c++operator-overloadingvariadic-templates

Expanding using declaratives for a conversion operator


The following code does not compile. What would be the correct syntax, and is it available?

I let struct overloaded inherit from its transformed template arguments, wrapping every argument into can_convert<T> before using it as a base class for overloaded. Now, I want to make all operator T() available in overloaded and therefore need a variadic using declarative. That is the point where I do not know whether this is possible in C++ and, if yes, what the syntax should look like.

#include <string>

template <typename T> 
struct can_convert_to 
{ 
    using type = T;
    operator T(); 
};

template<class... Ts>  
struct overloaded : can_convert_to<Ts>... { 
    // WHAT WOULD BE THE CORRECT SYNTAX HERE?
    using can_convert_to<Ts>::operator typename can_convert_to<Ts>::type()...; 
    };

template<typename... Ts> 
auto any_of() {
    overloaded<Ts...> o; 
    return o;
};

template<typename T>
concept has_set = requires(T t) {
    { t.set(
        any_of<std::string_view, std::string, char*>() 
    ) } -> std::same_as<void>;
};

struct Foo {};
static_assert(!has_set<Foo>);

struct Bar {
    void set(std::string_view);
    void set(std::string);
};
static_assert(has_set<Bar>);

struct Baz {
    void set(std::string);
};
static_assert(has_set<Baz>);

struct Bazz {
    void set(char const *);
};
static_assert(has_set<Bazz>);

Solution

  • Just remove the parentheses after can_convert_to<Ts>::type, since the syntax is:

    using typename(optional) nested-name-specifier unqualified-id ;

    As stated in https://en.cppreference.com/w/cpp/language/using_declaration. The nested-name-specifier is a "a sequence of names and scope resolution operators ::, ending with a scope resolution operator", specifying the namespace you wanna query the id in, and unqualified-id is basically a bare identifier specifying the name that you wanna introduce. The using declaration doesn't really care about the actual function signature, all overloads are introduced.

    template <class... Ts>
    struct overloaded : can_convert_to<Ts>... {
        using can_convert_to<Ts>::operator typename can_convert_to<Ts>::type...;
    };
    

    https://godbolt.org/z/Td7sxE6YT


    And, you might have noticed that this code still doesn't compile, as the static_assert regarding Bar on L34 fails. If you look into the reason, you'll find "call to member function 'set' is ambiguous". The "correcter" way to implement has_set would be like:

    #include <string>
    
    template <typename T, typename... Args>
    concept has_set_args = ((requires(T t, Args args) {
                                { t.set(args) } -> std::same_as<void>;
                            }) || ... || false);
    
    // Assume you only want the string-like ones
    template <typename T>
    concept has_set = has_set_args<T, std::string_view, std::string, const char *>;
    
    struct Foo {};
    static_assert(!has_set<Foo>);
    
    struct Bar {
        void set(std::string_view);
        void set(std::string);
    };
    static_assert(has_set<Bar>);
    
    struct Baz {
        void set(std::string);
    };
    static_assert(has_set<Baz>);
    
    struct Bazz {
        void set(char const *);
    };
    static_assert(has_set<Bazz>);
    

    The has_set_args inspects all of the types to see if any of them could be used as arguments in set. Also, you might want to just provide has_set with std::string_view and char*, as std::string can be implicitly converted to std::string_view.