Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

c++ template type deduction fail in cast operator

I have simplified my somewhat more difficult problem to this:

http://coliru.stacked-crooked.com/a/2660b33492651e92

#include <iostream>
#include <string>
#include <type_traits>

template<typename C>
struct get_type
{
    C operator()() const = delete;
};

template<>
struct get_type<std::string>
{
    std::string operator()() const { return "asd"; }
};

template<>
struct get_type<size_t> {
    size_t operator()() const { return 6; }
};

struct S
{
    S(){}
    template<typename T>
    operator T() { return get_type<T>{}(); }
};

struct A
{
    A() :s{S{}}, n{S{}} {}
    std::string s;
    size_t n;
};

int main()
{
    A a;
    std::cout << "Spock out." << std::endl;
}

This generates the following error:

'In instantiation of 'S::operator T() [with T = char]':'...

Why is T is deduced to char and not std::string?

Edit:

@YSC's answer seems to be correct: https://stackoverflow.com/a/46608866/4723722

I edited the post to add a solution: http://coliru.stacked-crooked.com/a/06d31d981acd2544

struct S
{
    S(){}
    template<typename T>
    explicit operator T() { return get_type<T>{}(); }
};
like image 616
TmsKtel Avatar asked Oct 06 '17 15:10

TmsKtel


Video Answer


1 Answers

Here:

A() : s( S{} ), ...

the construction of A::s from a S instance is ambiguous because for every type T for witch std::is_constructible<std::string, T>, the template function S::operator T() is a possible conversion path from S to T to std::string.

It seams your version of GCC starts by testing with T = char. clang list multiple candidates: http://coliru.stacked-crooked.com/a/17e247cca8b79c77:

/usr/local/bin/../lib/gcc/x86_64-pc-linux-gnu/7.2.0/../../../../include/c++/7.2.0/bits/basic_string.h:413:7: note: candidate constructor
      basic_string(const _Alloc& __a) _GLIBCXX_NOEXCEPT
      ^
/usr/local/bin/../lib/gcc/x86_64-pc-linux-gnu/7.2.0/../../../../include/c++/7.2.0/bits/basic_string.h:421:7: note: candidate constructor
      basic_string(const basic_string& __str)
      ^
/usr/local/bin/../lib/gcc/x86_64-pc-linux-gnu/7.2.0/../../../../include/c++/7.2.0/bits/basic_string.h:493:7: note: candidate constructor
      basic_string(const _CharT* __s, const _Alloc& __a = _Alloc())
      ^
/usr/local/bin/../lib/gcc/x86_64-pc-linux-gnu/7.2.0/../../../../include/c++/7.2.0/bits/basic_string.h:515:7: note: candidate constructor
      basic_string(basic_string&& __str) noexcept
      ^
/usr/local/bin/../lib/gcc/x86_64-pc-linux-gnu/7.2.0/../../../../include/c++/7.2.0/bits/basic_string.h:542:7: note: candidate constructor
      basic_string(initializer_list<_CharT> __l, const _Alloc& __a = _Alloc())

As OP found by themself, making the conversion operator of S explicit solves the ambiguity (demo):

struct S
{
    S(){}
    template<typename T>
    explicit operator T() { return get_type<T>{}(); }
};

And this works (as user AndyG found) because under [over.match.copy] one can read:

When initializing a temporary to be bound to the first parameter of a constructor where the parameter is of type “reference to possibly cv-qualified T” and the constructor is called with a single argument in the context of direct-initialization of an object of type “cv2 T”, explicit conversion functions are also considered.

like image 148
YSC Avatar answered Dec 05 '22 11:12

YSC