TL;DR: My question is that requires {...} can be used as a constexpr bool expression by the standard?
I haven't found anything about that in the standard, but it simplifies a lot and results a much cleaner code. For example in SFINAE instead of enable_if, or some ugly typename = decltype(declval<>()...), or something else, it is a simple clean requires-expression.
This is my example:
#include <type_traits>
struct foo { typedef int type; };
struct bar { ~bar() = delete; };
/**
* get_type trait, if T::type is valid, get_type<T>::type
* equal to T::type, else void
*/
// T::type is valid
template<typename T, bool = requires{typename T::type;}>
struct get_type : std::type_identity<typename T::type> {};
// T::type is invalid
template<typename T>
struct get_type<T, false> : std::type_identity<void> {};
/// Template alias, this is buggy on GCC 11.1 -> internal compiler error
template<typename T>
using get_type_t = typename get_type<T>::type;
// Tests
static_assert(std::is_same_v<get_type_t<foo>, int>);
static_assert(std::is_same_v<get_type_t<bar>, void>);
/**
* Destructible trait
*
* In libstdc++-v3 this is the implementation for the testing
*
* struct __do_is_destructible_impl
* {
* template <typename _Tp, typename = decltype(declval<_Tp &>().~_Tp())>
* static true_type __test(int);
*
* template <typename>
* static false_type __test(...);
* };
*/
// This is the same:
template<typename T>
struct my_destructible_impl : std::bool_constant< requires(T t) { t.~T(); } >
{};
// Tests
static_assert(my_destructible_impl<foo>::value);
static_assert(!my_destructible_impl<bar>::value);
I found that it will evaluate to true or false if I'm correct:
The substitution of template arguments into a requires-expression used in a declaration of a templated entity may result in the formation of invalid types or expressions in its requirements, or the violation of semantic constraints of those requirements. In such cases, the requires-expression evaluates to false and does not cause the program to be ill-formed. The substitution and semantic constraint checking proceeds in lexical order and stops when a condition that determines the result of the requires-expression is encountered. If substitution (if any) and semantic constraint checking succeed, the requires-expression evaluates to true.
So I would like to ask if requires {...} can be safely used as a constexpr bool expression as in my example, or not? Because based on cppreference.com I'm not 100% sure, but I feel like it is and it compiles with clang and GCC. However in the standard I haven't found anything about that (or maybe I just can't use ctrl+f properly...). And I haven't found anything where someone use the requires-expression like this...
requires {...} is a requires-expression and according to expr.prim.req/p2 it is a prvalue:
A requires-expression is a prvalue of type bool whose value is
described below. Expressions appearing within a requirement-body are
unevaluated operands.
So yes, you can use it in a constexpr bool context.
Related
Why is the following code behaving as commented?
struct S
{
template<typename T, typename = std::enable_if_t<!std::is_constructible_v<S, T>>>
S(T&&){}
};
int main() {
S s1{1}; // OK
int i = 1;
S s2{i}; // OK
static_assert(std::is_constructible_v<S, int>); // ERROR (any compiler)
}
I get that for the constructor to be enabled, the assertion must be false. But S still is constructed from int in the example above! What does the standard say and what does the compiler do?
I would assume that before enabling the template constructor, S in not constructible so std::is_constructible<S, int> instantiates to false. That enables the template constructor but also condemns std::is_constructible<S, int> to always test false.
I've also tested with my own (pseudo?) version of std::is_constructible:
#include <type_traits>
template<typename, typename T, typename... ARGS>
constexpr bool isConstructibleImpl = false;
template<typename T, typename... ARGS>
constexpr bool isConstructibleImpl<
std::void_t<decltype(T(std::declval<ARGS>()...))>,
T, ARGS...> =
true;
template<typename T, typename... ARGS>
constexpr bool isConstructible_v = isConstructibleImpl<void, T, ARGS...>;
struct S
{
template<typename T, typename = std::enable_if_t<!isConstructible_v<S, T>>>
S(T&&){}
};
int main() {
S s1{1}; // OK
int i = 1;
S s2{i}; // OK
static_assert(std::is_constructible_v<S, int>); // OK
}
I suppose that it is because now std::is_constructible is not sacrificed for SFINAE in the constructor. isConstructible is sacrificed instead.
That brings me to a second question: Is this last example a good way to perform SFINAE on a constructor without corrupting std::is_constructible?
Rational: I ended up trying that SFINAE pattern to tell the compiler not to use the template constructor if any of the other available constructors matches (especially the default ones), even imperfectly (e.g., a const & parameter should match a & argument and the template constructor should not be considered a better match).
Your first code example is undefined behaviour, because S is not a complete type within the declaration of itself. std::is_constructible_v however requires all involved types to be complete:
See these paragraphs from cppreference.com:
T and all types in the parameter pack Args shall each be a complete
type, (possibly cv-qualified) void, or an array of unknown bound.
Otherwise, the behavior is undefined.
If an instantiation of a template above depends, directly or
indirectly, on an incomplete type, and that instantiation could yield
a different result if that type were hypothetically completed, the
behavior is undefined.
This makes sense because in order for the compiler to know if some type can be constructed it needs to know the full definition. In your first example, the code is kind of recursive: the compiler needs to find out if S can be constructed from T by checking the constructors of S which themselves depend on is_constructible etc.
This question is due to insane curiosity rather than an actual problem.
Consider the following code:
template<typename...>
struct type_list {};
template<typename, typename = void>
struct test_class;
template<typename... T>
struct test_class<type_list<T...>> {
static constexpr auto value = false;
};
template<typename T>
struct test_class<type_list<T>, std::enable_if_t<std::is_same_v<T, int>>> {
static constexpr auto value = true;
};
int main() {
static_assert(!test_class<type_list<double, char>>::value);
static_assert(test_class<type_list<int>>::value);
}
This fails with the error:
ambiguous partial specializations of 'test_class<type_list>'
If I changed the second specialization to something that doesn't work from a functional point of view, the error would go away:
template<typename T>
struct test_class<type_list<T>> {
static constexpr auto value = true;
};
Similarly, if I use the alias template void_t, everything works as expected:
template<typename T>
struct test_class<type_list<T>, std::void_t<std::enable_if_t<std::is_same_v<T, int>>>> {
static constexpr auto value = true;
};
Apart from the ugliness of combining void_t and enable_if_t, this also gets the job done when there is a single type that differs from int, ie for a static_assert(!test_class<type_list<char>>::value) (it wouldn't work in the second case instead, for obvious reasons).
I see why the third case works-ish, since the alias template is literally replaced with void when the condition of the enable_if_t is satisfied and type_list<T> is more specialized than type_list<T...> (right?).
However, I would have expected the same also for the following:
template<typename T>
struct test_class<type_list<T>, std::enable_if_t<std::is_same_v<T, int>>> {
static constexpr auto value = true;
};
At the end of the day, std::enable_if_t<std::is_same_v<T, int>> somehow is void when the condition is statisfied (ok, technically speaking it's typename blabla::type, granted but isn't ::type still void?) and therefore I don't see why it results in an ambiguous call. I'm pretty sure I'm missing something obvious here though and I'm curious to understand it now.
I'd be glad if you could point out the standardese for this and let me know if there exists a nicer solution than combining void_t and enable_if_t eventually.
Let's start with an extended version of your code
template<typename, typename = void>
struct test_class;
template<typename T>
struct test_class<type_list<T>> {
static constexpr auto value = false;
};
template<typename... Ts>
struct test_class<type_list<Ts...>> {
static constexpr auto value = false;
};
template<typename T>
struct test_class<type_list<T>, std::enable_if_t<std::is_same_v<T, int>>> {
static constexpr auto value = true;
};
that is called with
test_class<type_list<int>>::value
Try it here!
The standard distinguishes between template parameters that are equivalent, ones that are only functionally equivalent and others which are not equivalent [temp.over.link]/5
Two expressions involving template parameters are considered equivalent if two function definitions containing the expressions would satisfy the one-definition rule, except that the tokens used to name the template parameters may differ as long as a token used to name a template parameter in one expression is replaced by another token that names the same template parameter in the other expression.
Two unevaluated operands that do not involve template parameters are considered equivalent if two function definitions containing the expressions would satisfy the one-definition rule, except that the tokens used to name types and declarations may differ as long as they name the same entities, and the tokens used to form concept-ids may differ as long as the two template-ids are the same ([temp.type]).
Two potentially-evaluated expressions involving template parameters that are not equivalent are functionally equivalent if, for any given set of template arguments, the evaluation of the expression results in the same value.
Two unevaluated operands that are not equivalent are functionally equivalent if, for any given set of template arguments, the expressions perform the same operations in the same order with the same entities.
E.g. std::enable_if_t<std::is_same_v<T, T>> and void are only functionally equivalent: The first term will be evaluated to void for any template argument T. This means according to [temp.over.link]/7 code containing two specialisations <T, void> and <T, std::enable_if_t<std::is_same_v<T, T>> is already ill-formed:
If the validity or meaning of the program depends on whether two constructs are equivalent, and they are functionally equivalent but not equivalent, the program is ill-formed, no diagnostic required.
In the code above std::enable_if_t<std::is_same_v<T, int>> is not even functionally equivalent to any of the other versions as it is in general not equivalent to void.
When now performing partial ordering [temp.func.order] to see which specialisation matches best your call this will result in an ambiguity as test_class is equally specialised [temp.func.order]/6 in both cases (with either Ts={int}, void or T=int, std::enable_if_t<std::is_same_v<T, int>>, both resulting in int, void but you can't deduce them against each other) and therefore the compilation will fail.
On the other hand by wrapping std::enable_if_t with std::void_t, which is nothing more but an alias for void
template <typename T>
using void_t = void;
the partial ordering will succeed as in this case the compiler will already know the type of the last template parameter is void in all cases, choosing test_class<T, std::void_t<std::enable_if_t<std::is_same_v<T,int>>>> with T=int as the most specialised as the non-variadic case (T=int, void) is considered more specialised than the variadic one Ts={int}, void (see e.g. temp.deduct.partial/8 or again this).
The following code:
#include <concepts>
template <typename T>
struct Foo
{
template <std::convertible_to<typename T::value_type> U> requires requires { typename T::value_type; }
void bar()
{
}
template <typename U>
void bar()
{
}
};
int main()
{
auto foo = Foo<float> {};
foo.bar<int>();
}
is rejected by GCC 11:
error: ‘float’ is not a class, struct, or union type
8 | void bar()
| ^~~
What I expected to happen was that the first definition of bar to be rejected due to an unsatisfied constraint and the second one to be selected. However, apparently when GCC tries to substitute float for T it fails to compile typename T::value_type before looking at the constraint. I can get the behaviour that I expected if I replace the definition with:
template <typename U> requires (requires { typename T::value_type; } && std::convertible_to<U, typename T::value_type>)
void bar()
{
}
which I find much less elegant.
Does the standard say that the first approach is illegal or is it a deficiency in the GCC implementation? If it's the former, is there a nicer way of writing this constraint (short of defining a new named concept like convertible_to_value_type_of)?
Edit: Just to clarify in the light of comments and the (now deleted) answer: I understand why this code would be rejected based on pre-C++20 rules. What I was getting at is that the addition of concepts to C++20 could have been an opportunity to relax the rules so that the compiler defers the verification of validity of something like typename T::value_type until it checks the constraints that might come in the rest of the definition. My question is really: were the rules relaxed in this manner?
The standard is quite clear that constraints are only substituted into at the point of use or when needed for declaration matching:
The type-constraints and requires-clause of a template
specialization or member function are not instantiated along with the
specialization or function itself, even for a member function of a
local class; substitution into the atomic constraints formed from them
is instead performed as specified in [temp.constr.decl] and
[temp.constr.atomic] when determining whether the constraints are
satisfied or as specified in [temp.constr.decl] when comparing
declarations.
This is a GCC bug. It appears that GCC does handle this correctly in a requires-clause so that can be a workaround:
template <class U>
requires std::convertible_to<U, typename T::value_type>
void bar()
{
}
I'm getting the error:
Error C2154 '_Ty': only enumeration type is allowed as an argument to compiler intrinsic type trait '__underlying_type'
I thought it shouldn't be resolving underlying_type to underlying_type, because I first check whether T is an enum. Here is the code:
template <typename T>
struct Foo
{
static inline constexpr bool isArgIntegral = std::is_integral<T>::value;
static inline constexpr bool isArgEnum = std::is_enum_v<T>;
using integral_underlying_type = std::conditional<isArgEnum, std::underlying_type_t<T>, T>;
};
int main()
Foo<int> g; // only enumeration type is allowed as an argument to compiler intrinsic type trait '__underlying_type'
}
So is it the case that in a call to std::conditional, instead of first checking the condition (1st argument), it creates the classes of the 2nd and 3rd arguments regardless of the condition, and hence why I'm getting the error that I can't call underlying_type with an 'int'?
How do I go about getting the integral type of the T template argument, whether it's an integral or an enum?
Edit: My next attempt is to place the typedef in an if constexpr:
if constexpr (std::is_enum_v<T>)
{
using integral_underlying_type = std::underlying_type_t<T>;
// Now std::underlying_type_t won't be called at all unless T is enum, right?
}
Unfortunately, std::integral_underlying_type is not SFINAE friendly until C++20.
You can then delay instantiation of std::underlying_type<T>::type:
template <typename T>
struct Foo
{
using integral_underlying_type =
typename std::conditional_t<std::is_enum_v<T>,
std::underlying_type<T>,
type_identity<T>>::type;
};
Notice the double ::type in std::conditional<cond, T1, T2>::type::type (hidden with _t). The extra ::type is done outside the conditional instead of inside (and so the need of type_identity).
Yes. The problem is that whether the condition isArgEnum is true or false (i.e. whether T is enum or not), std::underlying_type_t<T> has to be specified as the template argument for std::conditional.
You can apply partial specialization like
template <typename T, typename = void>
struct Foo
{};
template <typename T>
struct Foo<T, std::enable_if_t<std::is_enum_v<T>>>
{
using integral_underlying_type = std::underlying_type_t<T>;
};
template <typename T>
struct Foo<T, std::enable_if_t<std::is_integral_v<T>>>
{
using integral_underlying_type = T;
};
Consider the following code, which tries to determine existence of a nested typedef.
#include<type_traits>
struct foo;// incomplete type
template<class T>
struct seq
{
using value_type = T;
};
struct no_type{};
template<class T>
struct check_type : std::true_type{};
template<>
struct check_type<no_type> :std::false_type{};
template<class T>
struct has_value_type
{
template<class U>
static auto check(U const&)-> typename U:: value_type;
static auto check(...)->no_type;
static bool const value = check_type<decltype(check(std::declval<T>()))>::value;
using type = has_value_type;
};
int main()
{
char c[has_value_type<seq<foo>>::value?1:-1];
(void)c;
}
Now invoking has_value_type<seq>::value causes compilation error as invalid use of incomplete type seq<foo>::value_type.
does decltype needs a complete type in the expression? If not, how can I remove the error? I am using gcc 4.7 for compilation.
Your code is valid C++11, which defines that a toplevel function call that appears as a decltype operand does not introduce a temporary even when the call is a prvalue.
This rule specifically was added to make code as yours valid and to prevent instantiations of the return type (if it is a class template specialization) otherwise needed to determine the access restrictions of a destructor.
decltype requires a valid expression, and you certainly can have a valid expression that involves incomplete types. The problem in your case however is
template<class U>
auto check(U const&) -> typename U::value_type;
which has return type foo when U is seq<foo>. You can't return an incomplete type by value, so you end up with an ill-formed expression. You can use a return type of e.g. void_<typename U::value_type> (with template<typename T> struct void_ {};) and your test appears to work.