I've been working on a task we got by our professor, where I have to work around a variadic template class. The problem is, I cannot modify the class members within the recursive constructor. I cannot figure out why this is the case, as soon as it goes into the next constructor call, it will discard my changes on the variable.
What I have tried:
using pointer int* count
instead of int count
using a setter to set the counter
I have already googled around for hours, but cannot find a solving answer.
Header file "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};
}
The output I got:
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...
Counter(rest ...);
creates an unnamed temporary object, it does not recursively invoke constructor for this object. Each object is spawned with its own count
therefore you get stream of 1 1 1 1
If you want to delegate object initialization to different constructor then it should be present in member initialization list. This does not seem like a good idea though:
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";
}
As explained by VTT, calling Counter()
inside the body of the constructor create a new Counter()
object.
You can call, recursively, the contructors but you have to do this in the initialization list: look for "delegating contructors" for more informations.
I also would advise you against initialization (and modifications) of member object inside the body of contructors.
If your target is initialize count
with the number of the arguments and tmp
with the value of the last argument, I propose the following ("tag dispatching" based) solution
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"; }
};
You can also avoid tag-dispatching and constructor recursion delegating the recursion about rest...
to a method (maybe static
and also constexpr
, if you want) used to initialize 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"; }
};
Off Topic: to be precise, your Counter
class isn't "a variadic template class".
It's an ordinary (not template) class with one (two, in my first solution) variadic template constructor(s).
-- EDIT --
The OP asks
What if I need to get the count as a static const variable and an int array with the length of the counter within compile time and as class members? (Array will be filled with all constructor arguments) Is this within the C++ possibilitys?
A static const (maybe also constexpr
) make sense only if the counter is a common value between all the instances of the class.
At the moment doesn't make sense because your Counter
accept initialization lists of different length.
But suppose that number of the argument of the constructor is a template parameter (say N
)... in that case count
is simply N
and can be static constexpr
. You can define a std::array<int, N>
for the values (also a int[N]
but I suggest to avoid to use C-style arrays, when possible, and use std::array
instead) and, making the constructor constexpr
, you can impose the compile time initialization.
The following is a full compiling C++14 example (uses std::make_index_sequence
and std::index_sequence
that, unfortunately, are available only starting from C++14).
Observe that I've defined the f8
variable in main()
as constexpr
: only this way you can impose (pretending there isn't the as-is rule) that f8
is initialized compile-time
#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;
}
If you can use a C++17 enabled compiler, you can also use a deduction guide for foo
template <typename ... Args>
foo(Args...) -> foo<sizeof...(Args)>;
so there is no needs to explicate the template argument defining f8
// .......VVV no more "<8u>"
constexpr foo f3{ 2, 3, 5, 7, 11, 13, 17, 19 };
because it's deduced from the number of the argument of the constructor.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With