constraints with c++20 concepts - c++

I'm trying to understand C++20 concepts, in particular the example from here. Why is it an error if we're templatizing f with a concept that's stricter than allowed? In other words doesn't Integral4 also satisfy the Integral concept?
#include <type_traits>
#include <concepts>
template<typename T>
concept Integral = std::integral<T>;
template<typename T>
concept Integral4 = Integral<T> && sizeof(T) == 4;
template<template<Integral T1> typename T>
void f(){
}
template<typename T>
struct S1{};
template<Integral T>
struct S2{};
template<Integral4 T>
struct S3{};
void test(){
f<S1>(); // OK
f<S2>(); // OK
// error, S3 is constrained by Integral4 which is more constrained than
// f()'s Integral
f<S3>();
}
Error
<source>: In function 'void test()':
<source>:28:10: error: no matching function for call to 'f<template<class T> requires Integral4<T> struct S3>()'
28 | f<S3>();
| ~~~~~^~
<source>:11:6: note: candidate: 'template<template<class T1> class requires Integral<T1> T> void f()'
11 | void f(){
| ^
<source>:11:6: note: template argument deduction/substitution failed:
<source>:28:10: error: constraint mismatch at argument 1 in template parameter list for 'template<template<class T1> class requires Integral<T1> T> void f()'
28 | f<S3>();
| ~~~~~^~
<source>:28:10: note: expected 'template<class T1> class requires Integral<T1> T' but got 'template<class T> requires Integral4<T> struct S3'
Compiler returned: 1

f takes a template template parameter that is constrained on Integral. This means that f is allowed to use any type T which satisfies Integral on this template.
For example, short.
S3 is a type constrained on Integral4, which subsumes Integral. This means that, while any type U which satisfies Integral4 will also satisfy Integral, the reverse is not true. There are some types T which satisfy Integral but not Integral4
short for example is Integral, but it is unlikely to satisfy Integral4.
But f has the right to use short on the template it is provided, because that's what its signature says it can do. Since S3 provided only allows a subset of the types that f's signature allows it to use, you get a compile error.

Related

Type traits and template overload resolution

I'm writing a type trait to check that a given type is a specific container and I came across the following situation:
#include <array>
#include <cstddef>
template<typename T>
struct is_char_array_t final: std::false_type {};
template<std::size_t N>
struct is_char_array_t<std::array<char, N>> final: std::true_type {};
int main(){
if constexpr (is_char_array_t<42>) {
return 2;
}
}
This code doesn't compile, the error it gives is:
<source>: In function 'int main()':
<source>:10:37: error: type/value mismatch at argument 1 in template parameter list for 'template<class T> struct is_char_array_t'
10 | if constexpr (is_char_array_t<42>) {
| ^
<source>:10:37: note: expected a type, got '42'
<source>:10:38: error: expected unqualified-id before ')' token
10 | if constexpr (is_char_array_t<42>) {
| ^
ASM generation compiler returned: 1
<source>: In function 'int main()':
<source>:10:37: error: type/value mismatch at argument 1 in template parameter list for 'template<class T> struct is_char_array_t'
10 | if constexpr (is_char_array_t<42>) {
| ^
<source>:10:37: note: expected a type, got '42'
<source>:10:38: error: expected unqualified-id before ')' token
10 | if constexpr (is_char_array_t<42>) {
| ^
Execution build compiler returned: 1
The behavior is what I want, since it doesn't let anything that isn't a std::array<char, N> match the trait, but I'm left wondering why it works.
I would think that the template overload for std::size_t is a better match for the value 42 than the template that requires a type. So why doesn't the second, more specialized template match the given value?
Here's a godbolt with the example: https://godbolt.org/z/KdKvrPsx3
This is the template:
template<typename T>
struct is_char_array_t final: std::false_type {}; // (I)
And this is a specialization:
template<std::size_t N>
struct is_char_array_t<std::array<char, N>> final: std::true_type {};
The specialization is not a new template. It does not change the fact that is_char_array has one type argument. It merely specializes the primary template (I) for the case that T matches std::array<char,N>. The specialization starts with template <std::size_t N> because N is a "free parameter", you do not care what N is for the specialization as long as the type T is a std::array<char,N> for some N. In other words, it is a partial specialization. A full specialization would start with template <> and have no such "free parameter". You can identify the specialization by the is_char_array_t<std::array<char, N>> where the primary template only mentions the name of the template.
Your code does not really "work". The trait is used to yield either true or false, depending on the type that is used to instantiate it. Using a value is an error.
The primary template (the first one in your code) declares is_char_array_t as such:
template<typename T>
struct is_char_array_t;
That means is_char_array_t is a class template expecting exactly one template argument which should be a type template argument.
You are trying to give it 42 as template argument, which is a value, not a type. Therefore is_char_array_t<42> is wrong, no matter how is_char_array_t is implemented or partially specialized. Class templates also can't be overloaded, so this can never change.
Also, it is clearly not working. If it was working, then there wouldn't be any error message. if constexpr's condition would simply evaluate to false and the branch wouldn't be taken, so that the resulting program end with 0 status code instead.

Why does an optional argument in a template constructor for enable_if help the compiler to deduce the template parameter? [duplicate]

This question already has answers here:
SFINAE: std::enable_if as function argument
(2 answers)
Closed 10 months ago.
The minimal example is rather short:
#include <iostream>
#include <array>
#include <type_traits>
struct Foo{
//template <class C>
//Foo(C col, typename std::enable_if<true,C>::type* = 0){
// std::cout << "optional argument constructor works" << std::endl;
//}
template <class C>
Foo(typename std::enable_if<true, C>::type col){
std::cout << "no optional argument constructor works NOT" << std::endl;
}
};
int main()
{
auto foo = Foo(std::array<bool,3>{0,0,1});
}
The first constructor works as expected. However the second constructor does not compile and I get
error: no matching function for call to ‘Foo::Foo(std::array)’
However the given explanation
note: template argument deduction/substitution failed
does not help, as std::enable_if<true, C>::type should be C and such the first argument in both constructors should look exactly the same to the compiler. I'm clearly missing something. Why is the compiler behaving differently and are there any other solution for constructors and enable_if, which do not use an optional argument?
Complete error message:
main.cpp:18:45: error: no matching function for call to ‘Foo::Foo(std::array)’
18 | auto foo = Foo(std::array<bool,3>{0,0,1});
| ^
main.cpp:11:5: note: candidate: ‘template Foo::Foo(typename std::enable_if::type)’
11 | Foo(typename std::enable_if<true, C>::type col){
| ^~~
main.cpp:11:5: note: template argument deduction/substitution failed:
main.cpp:18:45: note: couldn’t deduce template parameter ‘C’
18 | auto foo = Foo(std::array<bool,3>{0,0,1});
| ^
main.cpp:5:8: note: candidate: ‘constexpr Foo::Foo(const Foo&)’
5 | struct Foo{
| ^~~
main.cpp:5:8: note: no known conversion for argument 1 from ‘std::array’ to ‘const Foo&’
main.cpp:5:8: note: candidate: ‘constexpr Foo::Foo(Foo&&)’
main.cpp:5:8: note: no known conversion for argument 1 from ‘std::array’ to ‘Foo&&’
Template argument deduction does not work this way.
Suppose you have a template and a function using a type alias of that template:
template <typename T>
struct foo;
template <typename S>
void bar(foo<S>::type x) {}
When you call the function, eg foo(1) then the compiler will not try all instantiations of foo to see if any has a type that matches the type of 1. And it cannot do that because foo::type is not necessarily unambiguous. It could be that different instantiations have the same foo<T>::type:
template <>
struct foo<int> { using type = int; };
template <>
struct foo<double> { using type = int; };
Instead of even attempting this route and potentially resulting in ambiguity, foo<S>::type x is a nondeduced context. For details see What is a nondeduced context?.
Template argument deduction fails because C appears in a Non-deduced context. The linked page lists
The nested-name-specifier (everything to the left of the scope resolution operator ::) of a type that was specified using a qualified-id
as a non-deduced context.
They also mention another example further down:
For example, in A<T>::B<T2>, T is non-deduced because of rule #1 (nested name specifier), and T2 is non-deduced because it is part of the same type name, but in void(*f)(typename A<T>::B, A<T>), the T in A<T>::B is non-deduced (because of the same rule), while the T in A<T> is deduced.
The other answers already explain why argument deduction did not work here. If you want to enable_if your constructor, you can simply put the condition in the template list like this:
struct Foo{
// your condition here ---v
template <class C, typename std::enable_if_t< true >* = nullptr>
Foo(C col) {
std::cout << "constructor" << std::endl;
}
};

Why does this SFINAE not work with enable_if when one conditional branch is inherited from the base class?

#include <bits/stdc++.h>
#include <type_traits>
// Type your code here, or load an example.
template <typename Types>
class C1 {
public:
using A=typename Types::A;
using B=typename Types::B;
template <typename Dummy = void>
inline typename std::enable_if<std::is_same<A, B>::value, Dummy>::type f() { }
};
template <typename Types>
class C2 : public C1<Types> {
public:
using A=typename Types::A;
using B=typename Types::B;
template <typename Dummy = void>
inline typename std::enable_if<!std::is_same<A, B>::value, Dummy>::type f() { }
};
template <typename Types>
class C3 : public C2<Types> {
public:
using A=typename Types::A;
using B=typename Types::B;
};
struct Types{
using A = int;
using B = int;
};
int main() {
C3<Types> c;
c.f();
return 0;
}
When I try to compile the above code when A and B are not same, I get the following error:
<source>: In function 'int main()':
<source>:42:9: error: no matching function for call to 'C3<Types>::f()'
42 | c.f();
| ^
<source>:23:77: note: candidate: 'template<class Dummy> typename std::enable_if<(! std::is_same<typename Types::A, typename Types::B>::value), Dummy>::type C2<Types>::f() [with Dummy = Dummy; Types = Types]'
23 | inline typename std::enable_if<!std::is_same<A, B>::value, Dummy>::type f() { }
| ^
<source>:23:77: note: template argument deduction/substitution failed:
<source>: In substitution of 'template<class Dummy> typename std::enable_if<false, Dummy>::type C2<Types>::f<Dummy>() [with Dummy = void]':
<source>:42:9: required from here
<source>:23:77: error: no type named 'type' in 'struct std::enable_if<false, void>'
Note that the code I have presented is not the exact code I use but a minimal reproducible example
EDIT: Put up a minimal reproducible example using godbolt in place of the earlier for a better understanding of the situation
As always with such problems, it falls down to the very definition of SFINAE. The S stands for "substitution", which happens into the template that we are trying to instantiate. That template is the member f, and not C.
Even though C is a template also, and both A and B are dependent types in C, they are not dependent type when f is instantiated. They are already known. As such, the condition std::is_same<A, B>::value is not value dependent on any template parameter of f. It doesn't depend on substation into f. This trips the following clause in the C++11 standard (taken from the last draft prior to publication):
[temp.res] (emphasis mine)
8 Knowing which names are type names allows the syntax of every
template definition to be checked. No diagnostic shall be issued for a
template definition for which a valid specialization can be generated.
If no valid specialization can be generated for a template definition, and that template is not instantiated, the template
definition is ill-formed, no diagnostic required.
This means that whatever Types is, if it doesn't uphold the condition of f, then the very definition of f (without even being instatiated), is already ill-formed whenever C is instantiated. A diagnostic is not required for this in general (since checking it is intractable in the general case), but compilers can diagnose it early often enough, and will tell you about the problem.
Now, as to how to fix it, just make the condition of f value dependent on its own template parameter. A simple re-write can be
template <bool Dummy = std::is_same<A, B>::value>
inline auto f(vector<int>& ctx, const string& r) ->
typename std::enable_if<Dummy>::type { }
Now the condition depends on substation in the correct context.
Of course, even if you fix the SFIANE problem, you still need to make sure the overload set is composed of the correct members. The f in C2 hides the f in C1. Add a using declaration to C2 so it's still a candidate
using C1<Types>::f;

Nested dependent names are not evaluated in template for concept

(Originally separated from this question.)
In the following code snippet,
#include <concepts>
template<
typename T,
typename value_type = typename T::value_type
>
concept check_type = std::default_initializable<T>;
struct Foo {};
template<check_type T>
void func(T t) {}
int main()
{
Foo foo;
func(foo);
}
struct Foo does not contain the type alias value_type but it compiles without an error with GCC.
See the result tested on the compiler explorer.
However, with Clang, it reports the following error message:
❯ clang++ -std=c++20 asdf.cpp
asdf.cpp:17:5: error: no matching function for call to 'func'
func(foo);
^~~~
asdf.cpp:12:6: note: candidate template ignored: constraints not satisfied [with T = Foo]
void func(T t) {}
^
asdf.cpp:5:39: note: because substituted constraint expression is ill-formed: no type named 'value_type' in 'Foo'
typename value_type = typename T::value_type
^
1 error generated.
Also see the result tested on the compiler explorer.
Is this a bug?
GCC is correct under the current wording.
Per [temp.deduct]/5, satisfaction checking is done on the associated constraints of the function template:
If the function template has associated constraints ([temp.constr.decl]), those constraints are checked for satisfaction ([temp.constr.constr]).
[temp.constr.decl]/3.2 specifies that the associated constraint is based on the normal form:
A declaration's associated constraints are defined as follows:
...
Otherwise, if there is a single introduced
constraint-expression, the associated constraints are the normal form
of that expression.
Since check_type is a concept, it is transparent to normalization ([temp.constr.normal]/1.4), and since the second template parameter of check_type is not used in its definition, that parameter does not appear in the normal form of the constraint expression. Therefore, the validity (or lack thereof) of T::value_type has no effect on satisfaction checking.
If you want the concept to check for value_type, it is more expressive (not to mention correct) to just check for value_type directly:
template <typename T>
concept check_type = std::default_initializable<T> && requires { typename T::value_type; };

Using function argument as part of a constant expression - gcc vs clang

Consider the following code snippet:
template <bool> struct B { };
template <typename T>
constexpr bool pred(T t) { return true; }
template <typename T>
auto f(T t) -> decltype(B<pred(t)>{})
{
}
clang++ (trunk) compiles the code
g++ (trunk) fails compilation with the following error:
src:7:34: error: template argument 1 is invalid
auto f(T t) -> decltype(B<pred(t)>{})
^
src:7:34: error: template argument 1 is invalid
src:7:34: error: template argument 1 is invalid
src:7:34: error: template argument 1 is invalid
src:7:34: error: template argument 1 is invalid
src:7:34: error: template argument 1 is invalid
src:7:25: error: invalid template-id
auto f(T t) -> decltype(B<pred(t)>{})
^
src:7:36: error: class template argument deduction failed:
auto f(T t) -> decltype(B<pred(t)>{})
^
src:7:36: error: no matching function for call to 'B()'
src:1:24: note: candidate: 'template<bool <anonymous> > B()-> B<<anonymous> >'
template <bool> struct B { };
^
src:1:24: note: template argument deduction/substitution failed:
src:7:36: note: couldn't deduce template parameter '<anonymous>'
auto f(T t) -> decltype(B<pred(t)>{})
^
live example on godbolt.org
Even though g++'s diagnostic is misleading, I assume that the problem here is that t is not a constant expression. Changing the code to...
decltype(B<pred(T{})>{})
...fixes the compilation error on g++: live example on godbolt.org
What compiler is behaving correctly here?
GCC is wrong. There is no rule that prevents using a function's parameters in a constant expression in this way.
However, you cannot use the value of the parameter in such a context, and the set of types T for which f is callable is quite restricted. To see why, we need to consider what constructs will be evaluated when evaluating the expression pred(t):
// parameters renamed for clarity
template <typename U>
constexpr bool pred(U u) { return true; }
template <typename T>
auto f(T t) -> decltype(B<pred(t)>{});
The evaluation semantics for the call pred(t) are as follows:
copy-initialize pred's parameter u from f's parameter t
evaluate the body of pred, which trivially creates a bool value true
destroy u
So, f is only callable for types T for which the above only involves constructs that are valid during constant evaluation (see [expr.const]p2 for the rules). The requirements are:
T must be a literal type
copy-initialization of u from t must be a constant expression, and in particular, must not perform an lvalue-to-rvalue conversion on any member of t (because their values are not known), and must not name any reference member of t
In practice, this means that f is callable if T is an empty class type with a defaulted copy constructor, or if T is a class type whose copy constructor is constexpr and does not read any members of its argument, or (strangely) if T is std::nullptr_t (although clang currently gets the nullptr_t case wrong).
The compiler is expecting a parameter in that context because it needs to evaluate the full (template overloaded) function type. Given the implementation of pred, any value would work in that location. Here it is binding the f parameter's template type to the argument.
The g++ compiler appears to be making a simplifying assumption that a template constexpr function will somehow be altered by any parameters unless they are also const, which, as you've demonstrated, and clang agrees, is not necessarily the case.
It all comes down to how deep inside the function implementation the compiler goes to mark the function as non-const due to non-const contribution to the return value.
Then there is the question of whether the function is instantiated and requires the compiler to actually compile the code vs performing template parsing which, at least with g++, appears to be a different level of compilation.
Then I went to the standard and they kindly allow the compiler writer to make exactly that simplifying assumption and the template function instantiation should only work for f<const T> or f <const T&>
constexpr` functions must have: each of its parameters must be
LiteralType
So the template code should compile but fail if instantiated with a non-const T.
t is not constexpr value, this mean pred(t) is not constexpr too.
You can't use it in B<pred(t)> because this need constexpr.
This version compile correctly:
template <bool> struct B { };
template <typename T>
constexpr bool pred(T t) { return true; }
template <typename T, T t>
auto f() -> decltype(B<pred(t)>{})
{
}
https://godbolt.org/g/ydbj1X
Another valid code is:
template <typename T>
auto f(T t) -> decltype(pred(t))
{
}
This is because you do not evaluate pred(t) only you get type information.
B<pred(t)> need evaluation of pred(t) other wise you will get B<true> or B<false>, for any normal value you can't to this.
std::integral_constant<int, 0>{} can work in Clang case is probably because its value build in as part of type and always is same. If we change code a bit:
template <typename T>
auto f(T t) -> decltype(B<pred(decltype(t){})>{})
{
return {};
}
both Clang and GCC compile it, in case std::integral_constant, both t and decltype(t){} have always same value.