Search code examples
c++templatesclang++deduction-guide

C++ deduction guide for tl::expected does not compile with clang, but compiles with gcc (strange error messages)


I am using tl::expected and want to define a deduction guide for creating tl::unexpected of particular type that I am using in a .cpp file: (very simplified example)

class MyError {
public:
        enum ErrorType : int {
            none,
            type1,
            type2
        };

        MyError (ErrorType type, const std::string& description)
        {

        }
};

template<typename T>
tl::unexpected (MyError::ErrorType, const T&)->tl::unexpected<MyError>;

This code compiles perfectly fine with GCC (tried version from 11 to 14) but fails to compile with Clang 14 to 18. Also, Clang gives strange error messages (as like the syntax is totally broken):

<source>:2476:35: error: expected ')'
 2476 | tl::unexpected (MyError::ErrorType, const T&)->tl::unexpected<MyError>;
      |                                   ^
<source>:2476:16: note: to match this '('
 2476 | tl::unexpected (MyError::ErrorType, const T&)->tl::unexpected<MyError>;
      |                ^
<source>:2476:16: error: cannot use parentheses when declaring variable with deduced class template specialization type
 2476 | tl::unexpected (MyError::ErrorType, const T&)->tl::unexpected<MyError>;
      |                ^
<source>:2476:26: error: declaration of variable 'ErrorType' with deduced type 'tl::unexpected' requires an initializer
 2476 | tl::unexpected (MyError::ErrorType, const T&)->tl::unexpected<MyError>;
      |                          ^
<source>:2476:46: error: expected ';' at end of declaration
 2476 | tl::unexpected (MyError::ErrorType, const T&)->tl::unexpected<MyError>;
      |                                              ^
      |                                              ;
<source>:2476:46: error: cannot use arrow operator on a type
5 errors generated.
Compiler returned: 1

After replacing MyError::ErrorType with int in the deduction guide:

template<typename T>
tl::unexpected (int, const T&)->tl::unexpected<MyError>;

the error message is (almost) completely different:

<source>:2476:17: error: expected unqualified-id
 2476 | tl::unexpected (int, const T&)->tl::unexpected<MyError>;
      |                 ^
<source>:2476:17: error: expected ')'
<source>:2476:16: note: to match this '('
 2476 | tl::unexpected (int, const T&)->tl::unexpected<MyError>;
      |                ^
2 errors generated.
Compiler returned: 1

However, a simple template example compiles without error in Clang:

template<typename T1, typename T2>
class C 
{
    private:
    T1 a;
    T2 b;
};

template<typename T>
C (int, const T&)->C<int, const T&>;

So, I have no idea where the issue is? In my deduction guide, in the tl::expected implementation or in clang (as the code compiles and runs perfectly fine when compiled by GCC)

Here is a compiler explorer link to the above example: https://godbolt.org/z/z3Ybrx1he

The example code is at the end, (line 2450 and below) after the tl::expected header contents. It looks like neither clang, nor gcc know about std::expected and therefore I am using tl::expected.


Solution

  • As the standard says in [temp.deduct.guide]/3:

    A deduction-guide shall inhabit the scope to which the corresponding class template belongs and, for a member class template, have the same access.

    So putting the user-defined deduction guide in the namespace scope works in both clang and gcc. But be aware of [namespace.std]/4.4:

    The behavior of a C++ program is undefined if it declares:

    • a deduction guide for any standard library class template.

    And I think we should treat someone else's class templates the same way. It seems much better to define a kind of wrapper and work with it:

    template<typename T>
    tl::unexpected<MyError> MyError::as_unexpected(ErrorType, const T&);