0

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:

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

  2. 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;
}
Markus Mayr
  • 3,568
  • 16
  • 38
  • @KerrekSB it would only be a variadic function if the ellipse were after a comma as if it were a parameter, like `A(typename T::B& args, ...);` – Seth Carnegie Oct 23 '12 at 17:13
  • @SethCarnegie: Oh, sorry, I misread the code. Never mind. – Kerrek SB Oct 23 '12 at 17:20
  • 1
    @Markus well which one do you want to be called when you do `A x { B() }`? – Seth Carnegie Oct 23 '12 at 17:39
  • @SethCarnegie In fact, I would not care in this case, because both constructors do the same. It would feel more natural, if the more specialized constructor, i.e. `(1)` would be called. But I assume that the best thing would be if the compiler failed (more gracefully than `g++` does now). – Markus Mayr Oct 23 '12 at 17:58
  • same underlying problem as in http://stackoverflow.com/questions/11386042/confused-by-default-constructor-description-of-stdtuple-in-the-iso-c-standar – Johannes Schaub - litb Oct 23 '12 at 18:07

1 Answers1

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

Seth Carnegie
  • 70,115
  • 19
  • 169
  • 239
  • I can't see how this resolves the ambiguity. Apart from that `A x(B())` would only be considered as a function declaration at global scope? Furthermore, `B()` is not a type, therefore `A X(B())` is not a function declaration. – Markus Mayr Oct 23 '12 at 17:20
  • @MarkusMayr function declarations are allowed at function scope, not just global. Also, `B()` is a valid way to declare a parameter (a function pointer). Look at [this question](http://stackoverflow.com/a/12590078/726361) – Seth Carnegie Oct 23 '12 at 17:23
  • Okay, thanks. I'll take a look at it. But the problem remains the same, of course, even if I change that part of the code to `A X{B()}` or `A X((B()))`. – Markus Mayr Oct 23 '12 at 17:25
  • Great answer! I also learned something about SFINAE and constructors. Thanks! – Markus Mayr Oct 23 '12 at 18:02