5

The following works fine on Visual C++ 2015 Update 2. Note that A is non-copyable and A::A is explicit.

#include <iostream>
#include <tuple>

struct A
{
    explicit A(int i)
    {
        std::cout << i << " ";
    }

    // non-copyable
    A(const A&) = delete;
    A& operator=(const A&) = delete;
};


template <class... Ts>
struct B
{
    std::tuple<Ts...> ts;

    B(int i)
      : ts((sizeof(Ts), i)...)
    {
    }
};


int main()
{
    B<A, A, A, A> b(42);
}

The goal is to pass the same argument to all tuple elements. It correctly outputs:

42 42 42 42

However, it fails to compile on g++ 4.9.2. Among the many messages is the tuple constructor overload that I think should be called:

In instantiation of ‘B<Ts>::B(int) [with Ts = {A, A, A, A}]’:
    33:24:   required from here
    25:30: error: no matching function for call to
       ‘std::tuple<A, A, A, A>::tuple(int&, int&, int&, int&)’
          : ts((sizeof(Ts), i)...)

[...]

/usr/include/c++/4.9/tuple:406:19: note: template<class ... _UElements, class>
    constexpr std::tuple< <template-parameter-1-1> >::tuple(_UElements&& ...)
     constexpr tuple(_UElements&&... __elements)
               ^
/usr/include/c++/4.9/tuple:406:19: note:   template argument deduction/substitution failed:
/usr/include/c++/4.9/tuple:402:40: error: no type named ‘type’ in
    ‘struct std::enable_if<false, void>’
       template<typename... _UElements, typename = typename

The function signature is incomplete in the message, but it refers to this one:

template<typename... _UElements, typename = typename               
   enable_if<__and_<is_convertible<_UElements,
                   _Elements>...>::value>::type>                        
explicit constexpr tuple(_UElements&&... _elements)                      
     : _Inherited(std::forward<_UElements>(__elements)...) { }    

My understanding is that is_convertible fails for an explicit constructor. g++ 5.1 and clang 3.5 have similar error messages.

Now, in C++14, 20.4.2.1/10 says: "This constructor shall not participate in overload resolution unless each type in UTypes is implicitly convertible to its corresponding type in Types". This gives me the impression that g++ and clang actually have this right and that Visual C++ is overly permissive.

[edit: It appears that C++17 has removed this restriction and that Visual C++ 2015 follows it. It now says: "This constructor shall not participate in overload resolution unless [...] is_constructible<Ti, Ui&&>::value is true for all i." It looks like "is implicitly convertible" was changed to "is_constructible". However, I still need a C++14 solution.]

I tried removing explicit from the constructor (I'd prefer to keep it). Visual C++ again compiles fine, but both g++ and clang complain about the deleted copy constructor. Because int is now implicitly convertible to an A, I seem to end up in

explicit constexpr tuple(const Types&...)

which would implicitly convert the ints into a bunch of As and then try to copy them. I'm actually not sure how I would ever be able to use the other constructor.

In C++14, how can I get tuple to initialize its elements by passing the same argument to each constructor if the constructors are explicit?

isanae
  • 3,253
  • 1
  • 22
  • 47
  • This is close to [this one](http://stackoverflow.com/questions/22560100/how-to-initialize-all-tuple-elements-by-the-same-arguments), but the solutions don't seem to work for `explicit` constructors. – isanae Apr 12 '16 at 18:04
  • 2
    Seems a bug in *libstdc++*, works with *libc++* [Demo without explicit](http://coliru.stacked-crooked.com/a/f8280383ed46e9d2) and [with](http://coliru.stacked-crooked.com/a/94f4f46db6fa165e) – Jarod42 Apr 12 '16 at 18:29
  • @Jarod42 My reading of 20.4.2.1/1 would be that libstdc++ actually has it right, no? – isanae Apr 12 '16 at 18:49
  • I read non official [tuple constructor](http://en.cppreference.com/w/cpp/utility/tuple/tuple) and the requirements make sense. The `!is_convertible` part is to be `explicit` for the `tuple`. – Jarod42 Apr 12 '16 at 19:03
  • `sizeof` returns `std::size_t`, an unsigned type but you are accepting a signed type `int` as the parameter. – Nowhere Man Apr 12 '16 at 19:09
  • @NowhereMan The `sizeof` is merely to get a context where parameter pack expansion is valid. Its value is unused. – isanae Apr 12 '16 at 19:13
  • @Jarod42 I updated my question. Looks like cppreference is citing C++17, which changed the requirements for that constructor. – isanae Apr 12 '16 at 19:21

1 Answers1

2

In C++ 14, there doesn't seem to be any way of initializing tuple elements when constructors are explicit, because of the is_convertible requirement. I ended up writing a bare-bones implementation of std::tuple myself that is used on C++14 implementations, such as on Debian 8.

isanae
  • 3,253
  • 1
  • 22
  • 47