Конструктор шаблонов variadic моего класса не может изменять членов моего класса. Почему это так? - PullRequest
20 голосов
/ 13 апреля 2019

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

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

  • с помощью указателя 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};
}

Выход Iполучил:

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...

Ответы [ 2 ]

24 голосов
/ 13 апреля 2019

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";
}
12 голосов
/ 13 апреля 2019

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

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

- EDIT -

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

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

Статический констант (возможно, также 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

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

, поэтому нет необходимости объяснять аргумент шаблонаопределяя f8

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...