Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

tuple (explicit?) constructors, overload resolution

61 views
Skip to first unread message

Latimerius

unread,
Mar 29, 2018, 12:18:47 PM3/29/18
to
Hello,

please consider the following code:


#include <functional>
#include <tuple>
#include <iostream>

struct C {
using Fn = std::function <void (int )>;
Fn mFn;

C (Fn && fn) : mFn (std::move (fn)) {}
C (std::tuple <Fn> && fn) : mFn (std::move (std::get <0> (fn))) {}
};

int main ()
{
C c1 ( [] (int i) { std::cout << i << std::endl; } );
}


It's not compilable as the constructor call in main() is apparently ambiguous. Two things that I don't understand about this:

1) why does the second C's ctor even apply? Shouldn't the corresponding std::tuple ctor be explicit?
2) assuming both C's ctors are applicable - shouldn't the first one be a better match? I guess it only requires a single conversion (from lambda to C:::Fn) whereas the second one requires two (lambda to C::Fn as before and then from C::Fn to std::tuple).

Thanks in advance!

Alf P. Steinbach

unread,
Mar 30, 2018, 2:49:42 AM3/30/18
to
Both `tuple` and `function` have templated constructors that can take
just about any argument.

One way to disambiguate is to use a templated constructor that checks
the kind of argument (e.g. via a traits class like below) and forwards
to a constructor with explicit mention of the kind of argument:


#include <functional>
#include <tuple>
#include <iostream>
#include <type_traits>

namespace arg {
struct Some_type {};
struct Tuple {};
};

namespace c_arg {
template< class Arg > struct Kind_ { using T = arg::Some_type; };
template< class F > struct Kind_<std::tuple<F>> { using T =
arg::Tuple; };

template< class F > using Kind = typename Kind_<F>::T;
}

struct C {
using Fn = std::function <void (int )>;
Fn mFn;

C( arg::Some_type, Fn fn )
: mFn{ std::move( fn ) }
{ mFn( 1 ); }

C( arg::Tuple, std::tuple <Fn> t )
: mFn{ std::move( std::get<0>( t )) }
{ mFn( 2 ); }

template< class Arg >
C( Arg&& arg )
: C{ c_arg::Kind<std::remove_reference_t<Arg>>{},
std::forward<Arg>( arg ) }
{}
};

auto main()
-> int
{
auto lambda = [](int i) { std::cout << i << std::endl; };
std::tuple<std::function<void(int)>> t{ lambda };

C c1{ lambda };
C c2( t );
}


In practice this technique is used to disambiguate

* rvalue reference versus reference to const, for templated type,
* raw array versus pointer, for templated type.

Cheers & hth.,

- Alf

Latimerius

unread,
Mar 30, 2018, 4:32:48 PM3/30/18
to
Thanks for the advice on how to disambiguate - this is going to be useful. Could you also help me understand why the constructor call is ambiguous in the first place? Apparently I need to fix my understanding of overload resolution.

My current understanding leads me to believe that the tuple-taking constructor should require one more argument conversion and thus be a worse candidate. However, both g++ and clang++ disagree...

Latimerius

unread,
Apr 3, 2018, 6:27:38 AM4/3/18
to
On Friday, 30 March 2018 08:49:42 UTC+2, Alf P. Steinbach wrote:
> Both `tuple` and `function` have templated constructors that can take
> just about any argument.

Ah alright, I think I get it now. I sort of overlooked the templated std::tuple constructors that indeed can take just about anything. If I understand correctly now, in their presence the conversion from lambda to C::Fn is not visible to the initial overload resolution of C's constructors as it only happens afterwards, inside the templated std::tuple constructor. This would indeed make both C's constructors equivalent in terms of overload resolution, each requiring a single argument conversion.

Now the remaining question is, why the templated std::tuple constructors are not explicit as they, I believe, should be (until C++17 when they became conditionally explicit). This could be just an implementation defect but apparently, both libstdc++ and libc++ behave the same here even when compiling in C++11 mode. How likely is it that two high-quality independent implementations have the same defect?

Thanks!
0 new messages