Void_T "Can Implement Concepts"

void_t can implement concepts?

Yes, concepts lite basically dresses up SFINAE. Plus it allows deeper introspection to allow for better overloading. However that only works if the concept predicates are defined as concept bool. The improved overloading does not work with the current concept predicates, but conditional overloading can be used. Lets look how we can define predicates, constrain templates, and overload functions in C++14. This is kind of long, but it goes over how to create all of the tools needed to accomplish this in C++14.

Defining Predicates

First, it is kind of ugly to read the predicate with all the std::declval and decltype everywhere. Instead, we can take advantage of the fact that we can constrain a function using a trailing decltype(from Eric Niebler’s blog post here), like this:

struct Incrementable
{
template<class T>
auto requires_(T&& x) -> decltype(++x);
};

So if ++x is not valid, then the requires_ member function is not callable. So we can create a models trait that just checks if requires_ is callable using void_t:

template<class Concept, class Enable=void>
struct models
: std::false_type
{};

template<class Concept, class... Ts>
struct models<Concept(Ts...), void_t<
decltype(std::declval<Concept>().requires_(std::declval<Ts>()...))
>>
: std::true_type
{};

Constraining Templates

So when we want to constrain the template based on the concept, we will still need to use enable_if, but we can use this macro to help make it cleaner:

#define REQUIRES(...) typename std::enable_if<(__VA_ARGS__), int>::type = 0

So we can define an increment function that is constrained based on Incrementable concept:

template<class T, REQUIRES(models<Incrementable(T)>())>
void increment(T& x)
{
++x;
}

So if we call increment with something that is not Incrementable, we will get an error like this:

test.cpp:23:5: error: no matching function for call to 'incrementable'
incrementable(f);
^~~~~~~~~~~~~
test.cpp:11:19: note: candidate template ignored: disabled by 'enable_if' [with T = foo]
template<class T, REQUIRES(models<Incrementable(T)>())>
^

Overloading Functions

Now if we want to do overloading, we want to use conditional overloading. Say we want to create an std::advance using concept predicates, we could define it like this(for now we will ignore the decrementable case):

struct Incrementable
{
template<class T>
auto requires_(T&& x) -> decltype(++x);
};

struct Advanceable
{
template<class T, class I>
auto requires_(T&& x, I&& i) -> decltype(x += i);
};

template<class Iterator, REQUIRES(models<Advanceable(Iterator, int)>())>
void advance(Iterator& it, int n)
{
it += n;
}

template<class Iterator, REQUIRES(models<Incrementable(Iterator)>())>
void advance(Iterator& it, int n)
{
while (n--) ++it;
}

However, this causes an ambiguous overload(In concepts lite this would still be an ambiguous overload unless we change our predicates to refer to the other predicates in a concept bool) when its used with std::vector iterator. What we want to do is order the calls, which we can do using conditional overloading. It can be thought of writing something like this(which is not valid C++):

template<class Iterator>
void advance(Iterator& it, int n) if (models<Advanceable(Iterator, int)>())
{
it += n;
}
else if (models<Incrementable(Iterator)>())
{
while (n--) ++it;
}

So if the first function isn't called, it will call the next function. So lets start by implementing it for two functions. We will create a class called basic_conditional which accepts two function objects as template parameters:

struct Callable
{
template<class F, class... Ts>
auto requires_(F&& f, Ts&&... xs) -> decltype(
f(std::forward<Ts>(xs)...)
);
};

template<class F1, class F2>
struct basic_conditional
{
// We don't need to use a requires clause here because the trailing
// `decltype` will constrain the template for us.
template<class... Ts>
auto operator()(Ts&&... xs) -> decltype(F1()(std::forward<Ts>(xs)...))
{
return F1()(std::forward<Ts>(xs)...);
}
// Here we add a requires clause to make this function callable only if
// `F1` is not callable.
template<class... Ts, REQUIRES(!models<Callable(F1, Ts&&...)>())>
auto operator()(Ts&&... xs) -> decltype(F2()(std::forward<Ts>(xs)...))
{
return F2()(std::forward<Ts>(xs)...);
}
};

So now that means we need to define our functions as functions objects instead:

struct advance_advanceable
{
template<class Iterator, REQUIRES(models<Advanceable(Iterator, int)>())>
void operator()(Iterator& it, int n) const
{
it += n;
}
};

struct advance_incrementable
{
template<class Iterator, REQUIRES(models<Incrementable(Iterator)>())>
void operator()(Iterator& it, int n) const
{
while (n--) ++it;
}
};

static conditional<advance_advanceable, advance_incrementable> advance = {};

So now if we try to use it with an std::vector:

std::vector<int> v = { 1, 2, 3, 4, 5, 6 };
auto iterator = v.begin();
advance(iterator, 4);
std::cout << *iterator << std::endl;

It will compile and print out 5.

However, std::advance actually has three overloads, so we can use the basic_conditional to implement conditional that works for any number of functions using recursion:

template<class F, class... Fs>
struct conditional : basic_conditional<F, conditional<Fs...>>
{};

template<class F>
struct conditional<F> : F
{};

So, now we can write the full std::advance like this:

struct Incrementable
{
template<class T>
auto requires_(T&& x) -> decltype(++x);
};

struct Decrementable
{
template<class T>
auto requires_(T&& x) -> decltype(--x);
};

struct Advanceable
{
template<class T, class I>
auto requires_(T&& x, I&& i) -> decltype(x += i);
};

struct advance_advanceable
{
template<class Iterator, REQUIRES(models<Advanceable(Iterator, int)>())>
void operator()(Iterator& it, int n) const
{
it += n;
}
};

struct advance_decrementable
{
template<class Iterator, REQUIRES(models<Decrementable(Iterator)>())>
void operator()(Iterator& it, int n) const
{
if (n > 0) while (n--) ++it;
else
{
n *= -1;
while (n--) --it;
}
}
};

struct advance_incrementable
{
template<class Iterator, REQUIRES(models<Incrementable(Iterator)>())>
void operator()(Iterator& it, int n) const
{
while (n--) ++it;
}
};

static conditional<advance_advanceable, advance_decrementable, advance_incrementable> advance = {};

Overloading With Lambdas

However, additionally, we could use lambdas to write it instead of function objects which can help make it cleaner to write. So we use this STATIC_LAMBDA macro to construct lambdas at compile time:

struct wrapper_factor
{
template<class F>
constexpr wrapper<F> operator += (F*)
{
return {};
}
};

struct addr_add
{
template<class T>
friend typename std::remove_reference<T>::type *operator+(addr_add, T &&t)
{
return &t;
}
};

#define STATIC_LAMBDA wrapper_factor() += true ? nullptr : addr_add() + []

And add a make_conditional function that is constexpr:

template<class... Fs>
constexpr conditional<Fs...> make_conditional(Fs...)
{
return {};
}

Then we can now write the advance function like this:

constexpr const advance = make_conditional(
STATIC_LAMBDA(auto& it, int n, REQUIRES(models<Advanceable(decltype(it), int)>()))
{
it += n;
},
STATIC_LAMBDA(auto& it, int n, REQUIRES(models<Decrementable(decltype(it))>()))
{
if (n > 0) while (n--) ++it;
else
{
n *= -1;
while (n--) --it;
}
},
STATIC_LAMBDA(auto& it, int n, REQUIRES(models<Incrementable(decltype(it))>()))
{
while (n--) ++it;
}
);

Which is little more compact and readable than using the function object versions.

Additionally, we could define a modeled function to reduce down the decltype ugliness:

template<class Concept, class... Ts>
constexpr auto modeled(Ts&&...)
{
return models<Concept(Ts...)>();
}

constexpr const advance = make_conditional(
STATIC_LAMBDA(auto& it, int n, REQUIRES(modeled<Advanceable>(it, n)))
{
it += n;
},
STATIC_LAMBDA(auto& it, int n, REQUIRES(modeled<Decrementable>(it)))
{
if (n > 0) while (n--) ++it;
else
{
n *= -1;
while (n--) --it;
}
},
STATIC_LAMBDA(auto& it, int n, REQUIRES(modeled<Incrementable>(it)))
{
while (n--) ++it;
}
);

Finally, if you are interested in using existing library solutions(rather than rolling your own like I've shown). There is the Tick library that provides a framework for defining concepts and constraining templates. And the Fit library can handle the functions and overloading.

Beside concepts are there any other void_t replacements in C++20?

You can use the detected idiom, which is an abstraction over void_t. This is pretty much the closest you can get from concepts without them:

template <typename Default, typename AlwaysVoid, template<typename...> typename Op, typename... Args>
struct detector {
using value_t = std::false_type;
using type = Default;
};

template <typename Default, template<typename...> typename Op, typename... Args>
struct detector<Default, std::void_t<Op<Args...>>, Op, Args...> {
using value_t = std::true_type;
using type = Op<Args...>;
};

template <template<typename...> typename Op, typename... Args>
using is_detected = typename detail::detection::detector<nonesuch, void, Op, Args...>::value_t;

template <template<typename...> typename Op, typename... Args>
constexpr auto is_detected_v = detail::detection::detector<nonesuch, void, Op, Args...>::value_t::value;

template <template<typename...> typename Op, typename... Args>
using detected_t = typename detail::detection::detector<nonesuch, void, Op, Args...>::type;

template <typename Default, template<typename...> typename Op, typename... Args>
using detected_or = typename detail::detection::detector<Default, void, Op, Args...>::type;

It can then be used like this:

template<typename A, typename B, typename C>
using my_is_constructible_expr = decltype(A(std::declval<B>(), std::declval<C>()));

// Trait type that has ::value
template<typename A, typename B, typename C>
using my_is_constructible = is_detected<my_is_constructible_expr, A, B, C>;

// template variable, even closer to concepts
template<typename A, typename B, typename C>
inline constexpr bool my_is_constructible_v = is_detected_v<my_is_constructible_expr, A, B, C>;

void_t and trailing return type with decltype: are they completely interchangeable?

This is the metaprogramming equivalent of: should I write a function or should I just write my code inline. The reasons to prefer to write a type trait are the same as the reasons to prefer to write a function: it's more self-documenting, it's reusable, it's easier to debug. The reasons to prefer to write trailing decltype are the similar to the reasons to prefer to write code inline: it's a one-off that isn't reusable, so why put in the effort factoring it out and coming up with a sensible name for it?

But here are a bunch of reasons why you might want a type trait:

Repetition

Suppose I have a trait I want to check lots of times. Like fooable. If I write the type trait once, I can treat that as a concept:

template <class, class = void>
struct fooable : std::false_type {};

template <class T>
struct fooable<T, void_t<decltype(std::declval<T>().foo())>>
: std::true_type {};

And now I can use that same concept in tons of places:

template <class T, std::enable_if_t<fooable<T>{}>* = nullptr>
void bar(T ) { ... }

template <class T, std::enable_if_t<fooable<T>{}>* = nullptr>
void quux(T ) { ... }

For concepts that check more than a single expression, you don't want to have to repeat it every time.

Composability

Going along with repetition, composing two different type traits is easy:

template <class T>
using fooable_and_barable = std::conjunction<fooable<T>, barable<T>>;

Composing two trailing return types requires writing out all of both expressions...

Negation

With a type trait, it's easy to check that a type doesn't satisfy a trait. That's just !fooable<T>::value. You can't write a trailing-decltype expression for checking that something is invalid. This might come up when you have two disjoint overloads:

template <class T, std::enable_if_t<fooable<T>::value>* = nullptr>
void bar(T ) { ... }

template <class T, std::enable_if_t<!fooable<T>::value>* = nullptr>
void bar(T ) { ... }

which leads nicely into...

Tag dispatch

Assuming we have a short type trait, it's a lot clearer to tag dispatch with a type trait:

template <class T> void bar(T , std::true_type fooable) { ... }
template <class T> void bar(T , std::false_type not_fooable) { ... }
template <class T> void bar(T v) { bar(v, fooable<T>{}); }

than it would be otherwise:

template <class T> auto bar(T v, int ) -> decltype(v.foo(), void()) { ... }
template <class T> void bar(T v, ... ) { ... }
template <class T> void bar(T v) { bar(v, 0); }

The 0 and int/... is a little weird, right?

static_assert

What if I don't want to SFINAE on a concept, but rather just want to hard fail with a clear message?

template <class T>
struct requires_fooability {
static_assert(fooable<T>{}, "T must be fooable!");
};

Concepts

When (if?) we ever get concepts, obviously actually using concepts is much more powerful when it comes to everything related to metaprogramming:

template <fooable T> void bar(T ) { ... }

How to use C++20 concepts to do different things based on return type of a function?

Given an integer (or lvalue to one), would you not agree that it is convertible to a char? The constraints check exactly what you have them check for the types in your question.

One way to tackle it would be by constraint subsumption. Meaning (in a very hand wavy fashion) that if your concepts are written as a conjugation (or disjunction) of the same basic constraints, a compiler can normalize the expression to choose the "more specialized one". Applying it to your example..

template<typename T>
concept printable = requires (T t, std::size_t s)
{
{ t.size() } -> std::convertible_to<std::size_t>;
{ t[s] } -> std::convertible_to<char>;
};

template<typename T>
concept printable_num = printable<T> && requires (T t, std::size_t s)
{
{ t[s] } -> std::same_as<int&>;
};

Note how we used printable to define printable_num as the "more specific" concept. Running this example, we get the output you are after.

Is there a way to use concepts to disable member functions that would produce a reference to void?

No, it is unfortunately not possible. I wish what you wrote actually worked, and it would be the correct way to write it if there was one, but you just can't do it.

Your options are:

template <class T>
struct A {
// #1: a type trait that handles void for you
std::add_lvalue_reference_t<T> operator*() requires (!std::is_void_v<T>);

// #2: make it a template (with only one valid instantiation)
template <std::same_as<T> U=T>
requires (!std::is_void_v<U>)
U& operator*();

// #3: use auto, even though we know the type and it's easy to spell
auto& operator*() requires (!std::is_void_v<T>);
};

I dislike all three of them. Your mileage may vary.

What is the recommended way to simulate concepts and constraints?

It's a complicated topic and is not easy give an answer at your question.

Anyway, some observations/suggestions, without any pretense to being exhaustive.

(1) the static_assert() way

static_assert(LessThanComparable<U,T>, "oh this is not epic");

is a good solution if you want a function that works only with some types and give an error (a clear error, because you can choose the error message) if called with wrong types.

But usually is the wrong solution when you want an alternative. Isn't a SFINAE solution. So calling the function with arguments of wrong types gives an error and doesn't permit that another function is used in substitution.

(2) You're right about the

typename = std::enable_if_t</* some test */>>

solution. An user can explicit a third parameter by hand. Joking, I say that this solution can be "hijacked".

But isn't the only drawback of this solution.

Suppose you have two complementary foo() functions that have to be enabled/disabled via SFINAE; the first one when a test is true, the second one when the same test if false.

You can think that the following solution is dangerous (can be hijacked) but can work

/* true case */
template <typename T, typename = std::enable_if_t<true == some_test_v<T>>>
void foo (T t)
{ /* something with t */ }

/* false case */
template <typename T, typename = std::enable_if_t<false == some_test_v<T>>>
void foo (T t)
{ /* something with t */ }

Wrong: this solution simply doesn't works because you're enabling/disabling not the second typename but only the default value for the second typename. So you're not completely enabling/disabling the functions and the compiler have to consider two functions with the same signature (the signature of a function doesn't depends from default values); so you have a collision and obtain an error.

The following solutions, SFINAE over the returned type

std::enable_if_t<LessThanComparable<U,T>, void> order(T& a, U& b)

(also without void, that is the default type

std::enable_if_t<LessThanComparable<U,T>> order(T& a, U& b)

) or over the second type (following the Yakk's suggestion about a not-standard allowed void *)

template <typename T, typename U,
std::enable_if_t<LessThanComparable<U,T>>, bool> = true>

are (IMHO) good solutions because both of they avoid the hijacking risk and are compatible with two complementary functions with the same name and signature.

I suggest a third possible solution (no hijack-able, complementary compatible) that is the addition of a third defaulted value with SFINAE enabled/disabled type: something as

template <typename T, typename U>
void order(T& a, U& b, std::enable_if_t<LessThanComparable<U,T>> * = nullptr)

Another possible solution avoid at all SFINAE but use tag-dispatching; something as

template <typename T, typename U>
void order_helper (T & a, U & b, std::true_type const &)
{ if (b < a) { std::swap(a, b); } }

// something different if LessThanComparable is false ?
template <typename T, typename U>
void order_helper (T & a, U & b, std::false_type const &)
{ /* ???? */ }

template <typename T, typename U>
void order (T & a, U & b)
{ order_helper(a, b, LessThanComparable<U,T>{}); }

This in case LessThanComplarable inherit from std::true_type when the condition is true, from std::false_type when the condition is false.

Otherwise, if LessThanComparable gives only a boolean value, the call to order_helper() can be

order_helper(a, b, std::integral_constant<bool, LessThanComparable<U,T>>{});

(3) if you can use C++17, there is the if constexpr way that can avoid a lot of overloading

template <typename T, typename U>
void order(T& a, U& b)
{
if constexpr ( LessThanComparable<U, T> )
{
if ( b < a )
std::swap(a, b);
}
else
{
// what else ?
}
}

Using `void_t` to check if a class has a method with a specific signature

First, an id-expression naming a nonstatic member function can't be used as an unevaluated operand (such as the operand of decltype). Moreover, you should check for whether the entire function call expression is well formed, not just whether there is a member called getCount:

template< class, class = void >
struct hasGetCount : false_type { };

template< class T >
struct hasGetCount<T, VoidT<decltype(std::declval<T>().getCount())>>
: std::is_same<decltype(std::declval<T>().getCount()), int>::type { };

(Use declval<T&> if you want to check that getCount() can be called on an lvalue.)

If you just check for the existence of a getCount member, then you get a hard error if a member with that name exists but isn't callable (e.g., a data member).

Although at this point you might as well consider just using something like

template< class T >
struct hasGetCount<T, std::enable_if_t<std::is_same<decltype(std::declval<T>().getCount()), int>::value>> : std::true_type { };

instead of writing the decltype twice.

What would be the proper implementation of a concept describing a raw pointer (to objects)?

Just building up upon std::is_pointer there should be nothing wrong with

template <class T>
concept pointer =
std::is_pointer_v<T>;

Thanks to @cigien for that idea.

And I would say that in the simple backup example the choice of std::copyable instead is exactly right. As that's what you want to do with the backup, restore it again by copy.



Related Topics



Leave a reply



Submit