I've got a question regarding variadic templates. I've got a class that uses them in the following way:
template <class... T>
struct A {
A(B& arg); // (1)
A(typename T::B& args...); // (2)
};
typename T::B
is some type that is expected to be equal for all instances in the parameter pack. For ease of presentation, I refered to this type as B
. The class contains an instance of B
for each parameter in the parameter pack. The second constructor (2)
initializes these members. For convenience, there is a constructor (1)
that takes just one instance and initializes all members with the same instance.
The definition of the constructors is not really important for my problem, you can leave them empty. A more complete example is provided below.
Now, the problem is that the constructors conflict, if you initialize A with just one parameter. g++-4.7
was a little confused here and bailed out, but after taking a closer look at the class the problem was obvious.
Questions:
What does the standard say about the situation? Is this an ambiguity that should / can be resolved by the compiler or am I supposed to avoid this situation?
What are the best strategies for avoiding it? Not specifying something like the first constructor at all? I could also put the functionality of the first constructor in a static method, but that would make the API more inhomogeneous.
Thanks for your answers!
Full example:
struct B {};
struct C
{
using B = ::B;
};
template <class... T>
struct A
{
A(B& arg) {}
A(typename T::B & ... args) {}
};
int main()
{
A<C> x(B()); // Edit: Should be: A<C> X{B()}; But not related to the problem.
return 0;
}
A<C> x(B());
Is a function declaration, not an object declaration. You need to add parens or use braces:
A<C> x { B() };
or
A<C> x((B()));
Also, to pass a temporary B
to the constructor by reference, you have to make it const
:
A(const B& arg) { }
Then
A<C> x((B()));
Works fine.
Now to address the ambiguity problem, you need something like this:
#include <type_traits>
#include <iostream>
struct B {};
struct C
{
using B = ::B;
};
template <class... T>
struct A
{
A(const B& arg) {
std::cout << "one" << std::endl;
}
template<bool IsNotOne = sizeof...(T) != 1>
A(const typename std::enable_if<IsNotOne || !std::is_same<B, T>::value, T>::type::B&... args) {
std::cout << "multiple" << std::endl;
}
};
int main()
{
A<B> x1 { B() }; // prints one
A<C> x2 { B() }; // prints one
A<C, C> x3 { B(), B() }; // prints multiple
A<B, B> x4 { B(), B() }; // prints multiple
return 0;
}
We make the second constructor a template so we can rely on SFINAE.