Конструктор переменных шаблонов моего класса не может изменять членов моего класса, почему это так?

#c #c 11 #recursion #variadic-templates

#c #c 11 #рекурсия #variadic-шаблоны

Вопрос:

Я работал над задачей, которую мы получили от нашего профессора, где я должен работать с классом шаблонов variadic. Проблема в том, что я не могу изменять члены класса в рекурсивном конструкторе. Я не могу понять, почему это так, как только он переходит к следующему вызову конструктора, он отменяет мои изменения в переменной.

Что я пробовал:

  • использование указателя int* count вместо int count

  • использование установщика для установки counter

Я уже несколько часов гуглил, но не могу найти решающий ответ.

Заголовочный файл «test.h»:

 #include <cstdarg>
#include <iostream>

class Counter {
private:
    int count = 0;
    int tmp;

public:
    template <typename... Rest> Counter (int t, Rest... rest) {
        count  ;
        std::cout << "start recursive number " << count << "...n";
        Counter(rest ...);
        tmp = t;
        std::cout << "end recursive number " << count << "...n";
    }
    Counter (int t) {
        count  ;
        tmp = t;
        std::cout << "reached end of recursive ->  " << count << "n";
    }
};
  

main.cpp:

 #include "test.h"
int main () {
    Counter a {0, 1, 2, 3, 4};
}
  

Результат, который я получил:

 start recursive number 1...
start recursive number 1...
start recursive number 1...
start recursive number 1...
reached end of recursive ->  1
end recursive number 1...
end recursive number 1...
end recursive number 1...
end recursive number 1...
  

Комментарии:

1. Возможно, намерение здесь заключается в count том, чтобы быть static , я не уверен, считается ли это изменением переменной? В любом случае , я полагаю , что ваше намерение состоит в том , чтобы печатать tmp .

Ответ №1:

Counter(rest ...); создает неназванный временный объект, он не рекурсивно вызывает конструктор для этого объекта. Каждый объект порождается своим собственным count , поэтому вы получаете поток 1 1 1 1

Если вы хотите делегировать инициализацию объекта другому конструктору, то он должен присутствовать в списке инициализации элемента. Хотя это не кажется хорошей идеей:

 template <typename... Rest> Counter (int t, Rest... rest)
:   Counter{rest...}
{
    count  ;
    std::cout << "start recursive number " << count << "...n";
    tmp = t;
    std::cout << "end recursive number " << count << "...n";
}
  

Ответ №2:

Как объясняется VTT, вызов Counter() внутри тела конструктора создает новый Counter() объект.

Вы можете рекурсивно вызывать конструкторы, но вы должны сделать это в списке инициализации: найдите «делегирование конструкторов» для получения дополнительной информации.

Я бы также посоветовал вам не инициализировать (и не изменять) объект-член внутри тела конструкторов.

Если ваша цель инициализируется count количеством аргументов и tmp значением последнего аргумента, я предлагаю следующее (основанное на диспетчеризации тегов) решение

 class Counter
 {
   private:
      struct tag
       { };

      int count = 0;
      int tmp;

      Counter (tag tg, std::size_t c0, int t) : count(c0), tmp{t}
       { std::cout << "end: " << tmp << ", " <<count << "n"; }

      template <typename... Rest>
      Counter (tag t0, std::size_t c0, int t, Rest... rest) 
         : Counter{t0, c0, rest...} 
       { std::cout << "recursion: " << tmp << ", " << count << "n"; }

   public:
      template <typename... Rest>
      Counter (Rest... rest) : Counter{tag{}, sizeof...(Rest), rest...} 
       { std::cout << "start: " << tmp << ", " << count << "n"; }
 };
  

Вы также можете избежать диспетчеризации тегов и рекурсии конструктора, делегируя рекурсию rest... методу (возможно static , а также constexpr , если хотите), используемому для инициализации tmp

 class Counter
 {
   private:
      int count = 0;
      int tmp;

      static int getLastInt (int i)
       { return i; }

      template <typename ... Rest>
      static int getLastInt (int, Rest ... rs)
       { return getLastInt(rs...); }

   public:
      template <typename... Rest>
      Counter (Rest... rest)
         : count(sizeof...(Rest)), tmp{getLastInt(rest...)} 
       { std::cout << tmp << ", " << count << "n"; }
 };
  

Не по теме: если быть точным, ваш Counter класс не является «переменным шаблонным классом».

Это обычный (не шаблонный) класс с одним (двумя, в моем первом решении) конструкторами переменных шаблонов.

— РЕДАКТИРОВАТЬ —

OP спрашивает

Что, если мне нужно получить счетчик как статическую переменную const и массив int с длиной счетчика во время компиляции и как члены класса? (Массив будет заполнен всеми аргументами конструктора) Возможно ли это в C ?

Статическая константа (возможно, также constexpr ) имеет смысл, только если счетчик является общим значением для всех экземпляров класса.

На данный момент это не имеет смысла, потому что вы Counter принимаете списки инициализации разной длины.

Но предположим, что номер аргумента конструктора является параметром шаблона (скажем N )… в этом случае count это просто N и может быть static constexpr . Вы можете определить a std::array<int, N> для значений (также a int[N] , но я предлагаю избегать использования массивов в стиле C, когда это возможно, и использовать std::array вместо этого) и, создав конструктор constexpr , вы можете наложить инициализацию во время компиляции.

Ниже приведен полный пример компиляции C 14 (использование std::make_index_sequence и std::index_sequence , к сожалению, доступно только начиная с C 14).

Обратите внимание, что я определил f8 переменную в main() as constexpr : только так вы можете навязать (делая вид, что нет правила as-is), которое f8 инициализируется во время компиляции

 #include <array>
#include <iostream>
#include <type_traits>

template <typename T, std::size_t>
using getType = T;

template <std::size_t N, typename = std::make_index_sequence<N>>
struct foo;

template <std::size_t N, std::size_t ... Is>
struct foo<N, std::index_sequence<Is...>>
 {
   static_assert( sizeof...(Is), "!" );

   static constexpr auto count = N;

   const std::array<int, N> arr;

   constexpr foo (getType<int, Is> ... is) : arr {{ is ... }}
    { }
 };

int main ()
 {
   constexpr foo<8u>  f8 { 2, 3, 5, 7, 11, 13, 17, 19 };

   for ( auto const amp; i : f8.arr )
      std::cout << i << ' ';

   std::cout << std::endl;
 }
  

Если вы можете использовать компилятор с поддержкой C 17, вы также можете использовать руководство по вычету для foo

 template <typename ... Args>
foo(Args...) -> foo<sizeof...(Args)>;
  

таким образом, нет необходимости объяснять аргумент шаблона, определяющий f8

 // .......VVV  no more "<8u>"
constexpr foo  f3{ 2, 3, 5, 7, 11, 13, 17, 19 };
  

потому что он выводится из номера аргумента конструктора.

Комментарии:

1. Большое спасибо за подсказку, это очень помогло! Что, если мне нужно получить счетчик как статическую переменную const и массив int с длиной счетчика во время компиляции и как члены класса? (Массив будет заполнен всеми аргументами конструктора) Возможно ли это в C ?

2.@PrettyCoffee — Дайте мне подумать… Учтите, что static переменная является переменной, общей для всех экземпляров класса, поэтому static count переменная (более того, константа) имеет смысл, только если count значение является общим для всех экземпляров Counter . На данный момент это не имеет смысла, потому что вы можете вызывать конструктор разных экземпляров с разным количеством аргументов. Другое дело, если номер аргумента шаблона является параметром шаблона класса. Дайте мне несколько минут, и я подготовлю пример того, что я имею в виду.

3. @PrettyCoffee — ответ улучшен; надеюсь, это поможет.