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>);
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
.