Follow-up question to this question, where single argument use was not solved.
Assume the following C++ concept:
template <typename T>
concept has_set = requires(T t, std::string s) {
{ t.set(s) } -> std::same_as<void>; };
};
I cannot use a concept for parameter s
, e.g.
template <typename T>
concept has_set = requires(T t, std::convertible_to<std::string_view> s) {
{ t.set(s) } -> std::same_as<void>; };
};
will fail to compile. Is there any workaround or trick which can be applied here to let the following additional code compile?
std::string use(has_set auto & f) { /* ... use f.set(...) ... */ }
Please note that part of the solution is already given in https://stackoverflow.com/a/79130496/1528210
I'm offering an alternative answer, because I think the accepted one will work for most real cases, but isn't a 100% match to my understanding of the requirements.
My understanding of the requirements is: type Foo
should match has_set<Foo>
, if and only if it has a set
method returning void, which can be called with any argument implicitly convertible to string_view
. Basically, any valid set
implementation should look like: void set(std::string_view)
.
#include <concepts>
#include <string_view>
template<typename T>
concept has_set = requires {
// Use the static cast to support classes that have multiple set overloads
{ static_cast<void(T::*)(std::string_view)>(&T::set) };
};
struct Foo {};
static_assert(!has_set<Foo>);
struct Bar {
void set(std::string_view);
};
static_assert(has_set<Bar>);
struct Baz {
void set(std::string);
};
static_assert(!has_set<Baz>);
struct FooBarBaz {
void set(std::string_view);
void set(int);
};
static_assert(has_set<FooBarBaz>);
This matches the exact signature you'd like to see for set
(see godbolt). Thanks to StoryTeller - Unslander Monica in the comments for suggesting the cast to deal with overload sets.
template<typename T>
concept has_set = requires(T t, std::string_view sv) {
{ t.set(sv) } -> std::same_as<void>;
};
This is easy, but not entirely correct. See:
struct Foo {
Foo(std::string_view) {}
};
struct Bar {
void set(Foo) {}
};
static_assert(has_set<Bar>, "True, because Bar::set can be called with a string view");
static_assert(requires(Bar b, std::string s) { b.set(s); }, "Fails, because string -> string_view -> Foo is more than one implicit conversion");
This is clearly unexpected. I'd argue that in practice this is still good enough, because I can't imagine when this problem would come up.
namespace detail {
struct StringViewConvertible {
operator std::string_view();
};
}
template<typename T>
concept has_set = requires(T t, detail::StringViewConvertible svc) {
{ t.set(svc) } -> std::same_as<void>;
};
T::set
accepts a string_view.