Nested dependent names are not evaluated in template for concept - c++

(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; };

Related

Compiler discrepancy with simple meta function

When I try to use the following meta function to retrieve the first type of a tuple, the code can be compiled with GCC but not with Clang. I have two questions regarding the little snippet.
Is this legal C++ code? And why? Or why not?
Is there a workaround (or correct alternative) which works for both compilers?
#include <tuple>
template<typename>
struct first_type;
template<template<typename, typename...> typename T, typename T1, typename... Ts>
struct first_type<T<T1, Ts...>>
{ using type = T1; };
template<typename T>
using first_type_t = typename first_type<T>::type;
using tuple_type1 = first_type_t<std::tuple<int, int, double>>;
Live example
As requested, the error message generated by Clang:
<source>:12:1: error: implicit instantiation of undefined template
'first_type<std::tuple<int, int, double>>'
using first_type_t = typename first_type<T>::type;
^
<source>:14:21: note: in instantiation of template type alias
'first_type_t' requested here
using tuple_type1 = first_type_t<std::tuple<int, int, double>>;
^
<source>:4:8: note: template is declared here
struct first_type;
^
In conclusion:
As answered by IWonderWhatThisAPIDoes; to circumvent the compiler discrepancy completely, simply drop the requirement of the templated template argument to have at least a single type.
As pointed out by Nathan Oliver; if you need the first type of a tuple (or really any type given an index), simply use the std::tuple_element meta function instead.
As pointed out by HolyBlackCat; it seems the ruling that Clang enforces regarding templated template arguments, is stricter than technically required by the standard. This behavior can be disabled by passing the -frelaxed-template-template-args compiler flag.
Temporarily changing your struct declaration to (thus getting rid of the incomplete type):
template<typename>
struct first_type {};
Changes the error you get to:
no type named 'type' in 'first_type<std::tuple<int, int, double>>'
This gives us valuable information: the compiler chose the generic version of the template, implying that Clang does not consider tuple to be a valid template<typename,typename...>class to instantiate first_type with (which is true - template<typename,typename...>class takes one or more parameters, while a tuple can also be empty). As pointed out in the comments, this does not matter to the standard itself, but Clang rejects it on purpose.
A tuple is a template<typename...>class, so...
template<template<typename...> typename T, typename T1, typename... Ts>
struct first_type<T<T1, Ts...>> {
using type = T1;
};

Should `typename T::value_type` fail to compile when it is meant to be rejected by a constraint that comes later in the definition?

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()
{
}

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;

is `foo<Type1, Types...>` legal when foo is a struct which accepts single template parameter?

When playing with libstdcxx's test_property:
template<template<typename...> class Property,
typename Type1, typename... Types>
constexpr bool
test_property(typename Property<Type1, Types...>::value_type value)
{
return (Property<Type1, Types...>::value == value
&& Property<Type1, Types...>::type::value == value);
}
class Property accepts at least 1 template parameter(Type1).
Here is a use case:
static_assert(test_property<is_copy_assignable, ExceptMoveAssignClass>(false), "");
But I found clang doesn't work fine with this function:
prog.cc:29:3: error: no matching function for call to 'test_property'
test_property<std::is_copy_assignable, DeletedMoveAssignClass>(false);
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
prog.cc:12:1: note: candidate template ignored: substitution failure [with Property = std::is_copy_assignable, Type1 = DeletedMoveAssignClass]: too many template arguments for class template 'is_copy_assignable'
test_property(typename Property<Type1, Types...>::value_type value)
^ ~~~~~~~~
1 error generated.
The root cause is clang doesn't allow class Property to be class that only accepts one template parameter like template< class T > struct is_copy_assignable;. Once class Property is modified into Property<Type1>, it will compile successfully:
template<template<typename...> class Property, typename Type1>
constexpr bool
ya_test_property(typename Property<Type1>::value_type value)
{
return (Property<Type1>::value == value
&& Property<Type1>::type::value == value);
}
here is demo https://wandbox.org/permlink/LlL1o57Yted5WZo5
Of course, this function is from libstdcxx, so gcc can pass compile. Is this clang's bug?
Looks like a Clang bug if I'm interpreting [temp.variadic]/7 correctly:
When N is zero, the instantiation of the expansion produces an empty
list. Such an instantiation does not alter the syntactic
interpretation of the enclosing construct, even in cases where
omitting the list entirely would otherwise be ill-formed or would
result in an ambiguity in the grammar. [ Example:
template<class... T> struct X : T... { };
template<class... T> void f(T... values) {
X<T...> x(values...);
}
template void f<>(); // OK: X<> has no base classes
// x is a variable of type X<> that is value-initialized
 — end example ]
Similarly, while std::is_copy_assignable<ExceptMoveAssignClass , > is ill-formed, an empty pack should not put us in this state. It should be equivalent to std::is_copy_assignable<ExceptMoveAssignClass>, which is well-formed.
Of course, if the pack wasn't empty, then we'd be passing too many arguments, which is ill-formed. But that is not the case.

Templates instantiation confusion

This is my code to check whether class has member function begin or not :
template<typename T> struct has_begin
{
struct dummy {typedef void const_iterator;};
typedef typename std::conditional< has_iterator<T>::yes, T, dummy>::type TType;
typedef typename TType::const_iterator Iter;
struct fallBack{ Iter begin() const ; Iter end() const;};
struct checker : T, fallBack {};
template <typename B, B> struct cht;
template<typename C> static char check(cht< Iter (fallBack::*)() const, &C::begin>*); // problem is here
template<typename C> static char (&check(...))[2];
public:
enum {no = (sizeof(check<checker>(0))==sizeof(char)),
yes=!no};
};
If I change second argument of cht in check(cht< Iter (fallBack::*)() const, &C::begin>*); to
&checker::begin , This doesn't changes the semantic of code since cht's second template argument is always checker due to this enum {no = (sizeof(check<checker>(0))==sizeof(char))
but code change results in error now which are :
prog.cpp: In instantiation of 'has_begin<std::vector<int> >':
prog.cpp:31:51: instantiated from here
prog.cpp:23:38: error: reference to 'has_begin<std::vector<int> >::checker::begin' is ambiguous
I want to know what is the reason behind this behavior.
from the Wikipedia article about SFINAE - Substitution Failure is Not An Error:
[...] when creating a candidate set for overload resolution, some (or all)
candidates of that set may be the result of substituting deduced
template arguments for the template parameters. If an error occurs
during substitution, the compiler removes the potential overload from
the candidate set instead of stopping with a compilation error [...]
In your code as posted, an ambiguity error occurs while instantiating the function template check with parameter C == typename has_begin<T>::checker, and that substitution leads to the error, so the instantiation is simply removed from the overload set.
If you change your code, a similar ambiguaty error occurs with &checker::begin.
This time, however, it is not the result of substituting the template parameter C for the check function template. The subsitution of the template parameter T of struct has_begin is not relevant for the SFINAE rule, as that template has already been successfully instantiated.