Конструктор шаблонов 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

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 не является "классом вариационных шаблонов".

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

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

ОП спрашивает

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

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

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

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

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

Заметьте, что я определил переменную f8 в main() как constexpr: только так вы можете навязать (притворяясь, что нет правила "как есть"), что 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 & i : f8.arr )
      std::cout << i << ' ';

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

Если вы можете использовать компилятор с поддержкой C++ 17, вы также можете использовать руководство по foo для 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 };

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