2

I have trouble with the following construct:

  struct A {
    int min_version;
    int max_version;
    std::vector<int> nodes;

    A(std::vector<int> _nodes, int _min_version = 0, int _max_version = MAXINT)
      : nodes(_nodes), min_version(_min_version), max_version(_max_version)
    {};

  };

  static std::vector<A> a = list_of<A>
    (list_of (1) (2))
   ;

I get a compiler error that none of 2 overloads could convert all argument types (Visual Studio 2013). However, when I add values for one or both default arguments, it compiles fine:

  static std::vector<A> a = list_of<A>
    (list_of (1) (2), 1)
   ;

So, what's the trick to make both default arguments working?

Mike Lischke
  • 48,925
  • 16
  • 119
  • 181

1 Answers1

1

The problem with the one-argument version becomes much more obvious when you replace list_of<A>(...) by list_of(A(...)): the compiler error them becomes (GCC):

list.cc: In function ‘int main()’:
list.cc:18:30: error: call of overloaded ‘A(boost::assign_detail::generic_list<int>&)’ is ambiguous
   list_of (A (list_of (1) (2)));
                              ^
list.cc:18:30: note: candidates are:
list.cc:11:3: note: A::A(std::vector<int>, int, int)
   A(std::vector<int> _nodes, int _min_version = 0, int _max_version = INT_MAX)
   ^
list.cc:6:8: note: A::A(const A&)
 struct A {
        ^
list.cc:6:8: note: A::A(A&&)

And this makes sense, sort of: the generic untyped list* claims to be convertible to any type; it does not (and probably cannot) use SFINAE to reject types to which the conversion wouldn't work.

In your example, a conversion from the untyped list to A is a better match than a conversion from the untyped list to std::vector<int> to A. That conversion, then, of course does not work.

When you pass the second argument, the compiler knows that you cannot possibly be attempting to call the copy constructor.

My personal preference would be to add a dummy argument to the constructor, similar to the standard's use of piecewise_construct_t:

struct from_vector { };
struct A {
  ...
  A(std::vector<int> _nodes, ...) { ... }
  A(from_vector, std::vector<int> _nodes, ...) : this(_nodes, ...) { }
};

You can then write

list_of<A>( from_vector(), list_of (1) (2) )

* When I say "untyped list", I do of course realise that Boost has actually defined a type for that list, and that C++ expressions always have a type.

  • That's ... remarkable. But after your explaination it actually makes sense. Still, it's a mystery to me how a wrong deduction can be the better match if an obvious one exists. – Mike Lischke Aug 20 '14 at 12:28
  • @MikeLischke The problem is that the wrong one is even more obvious than the one you want. Essentially, you have `struct A { template operator T(); }`, `struct B { }`, and `struct C { C(B); }`. Based on the declarations alone, when you have an `A`, and you want a `C`, the most-obvious way to get there is to call the template conversion operator. Whether it'll actually work is not known if you merely have the declarations. What you want, the `C(B)` constructor after using the template conversion operator to get a `B`, is only the second-most obvious way. That said... –  Aug 20 '14 at 14:15
  • ...I think I was incorrect in suggesting that SFINAE would not be possible. In that case, it's simply poor design of `list_of`, and it *could* alternatively have been designed in a way that would let your code work, I now think. But since it wasn't, you're stuck working around the problem. –  Aug 20 '14 at 14:16