All standard references below refers to the current ISO Standard Working Draft, generated on 2020-06-22.
[dcl.fct]/18 states that [extract, emphasis mine]:
An abbreviated function template is a function declaration that has one or more generic parameter type placeholders ([dcl.spec.auto]). An abbreviated function template is equivalent to a function template ([temp.fct]) whose template-parameter-list includes one invented type template-parameter for each generic parameter type placeholder of the function declaration, in order of appearance. [...]
Such that the following to function declarations are areuably equivalent:
template <typename T>
void f(T);
void f(auto); // re-declaration
We may note, however, that the example of [dcl.fct]/18 states that
[...]
These declarations are functionally equivalent (but not equivalent) to the following declarations.
[...]
which may arguably (I'm unsure how interpret this) conflict with the equivalence statement in the prior passage.
Now, both GCC 10.1.0 and Clang 10.0.0 (as well as GCC:HEAD and Clang:HEAD) have some mixed behavior here. If we declare a function template and later define it (/re-declare it) using a mixed classical function template syntax with abbreviated function template syntax, Clang accepts most cases (defining a previously declared function) whereas GCC rejects all (sees the (attempted) re-declarations as separately declared functions with subsequent ambiguity failures in overload resolution):
// A1: Clang OK, GCC error
template <typename T>
void a(T);
void a(auto) {}
// B1: Clang OK, GCC error
void b(auto);
template <typename T>
void b(T) {}
// C1: Clang OK, GCC error
template <typename T, typename U>
void c(T, U);
void c(auto, auto) {}
// D1: Clang OK, GCC error
template <typename T, typename U>
void d(T, U);
template <typename T>
void d(T, auto) {}
// E1: Clang error, GCC error
template <typename T>
void e(T, auto);
template <typename T>
void e(auto, T) {}
int main() {
a(0); // Clang OK, GCC error.
b(0); // Clang OK, GCC error.
c(0, '0'); // Clang OK, GCC error.
d(0, '0'); // Clang OK, GCC error.
e(0, '0'); // Clang error, GCC error.
}
Curiously, if we make the function template a class member function template, both GCC and Clang accepts cases A1 through D1, but both rejects the final case E1 above:
// A2: OK
struct Sa {
template <typename T>
void a(T);
};
void Sa::a(auto) {}
// B2: OK
struct Sb {
void b(auto);
};
template <typename T>
void Sb::b(T) {}
// C2: OK
struct Sc {
template <typename T, typename U>
void c(T, U);
};
void Sc::c(auto, auto) {}
// D2: OK
struct Sd {
template <typename T, typename U>
void d(T, U);
};
template <typename T>
void Sd::d(T, auto) {}
// E2: Error
struct Se {
template <typename T>
void e(T, auto);
};
template <typename T>
void Se::e(auto, T) {}
with the following error messages:
GCC
error: no declaration matches 'void Se::e(auto:7, T)'
note: candidate is:
'template<class T, class auto:6> void Se::e(T, auto:6)'
Clang
error: out-of-line definition of 'e' does not match
any declaration in 'Se'
Now, the name of a type template parameter is not required to be consistent over re-declaration (or a definition) of a function template, as just names a generic type placeholder.
GCC's error message is particularly interesting, hinting that invented type template parameters are treated as concrete types rather than generic type placeholders.
Question:
Which of GCC and Clang are correct regarding cases A1 through D1 (rejecting and accepting, respectively)? Are GCC and Clang correct to reject case E2 above? What standard passage (of the working draft) unambiguously supports them?
This:
template <typename T>
void e(T, auto);
Translates to this:
template<typename T, typename U>
void e(T, U);
By contrast, this:
template <typename T>
void e(auto, T) {}
translates to:
template <typename T, typename U>
void e(U, T) {}
Remember that abbreviated function template parameters are placed at the end of the template parameter list. So those aren't declaring the same template, due to reversing the order of the template parameters. The first one declares one template, the second one declares and defines a different template.
You don't get a compile error just from this because the second definition is also a declaration. However, when you use a class member, out-of-member definitions are not declarations. Therefore, they must have a matching in-member declaration. Which they don't; hence the errors.
As for the others, the "functionally equivalent (but not equivalent)" text is a non-normative notation. The actual normative text that you quoted clearly states that these are "equivalent", not merely "functionally equivalent". And since the term "equivalent", per [temp.over.link]/7, is used to match declarations and definitions, it seems to me that the standard states that the A through D cases are fine.
What's weird is that this non-normative text was introduced by the same proposal that introduced the normative text. However, the proposal that it inherited the ConceptName auto syntax from seems clear that it means "equivalent", not "functionally equivalent".
So in terms of normative text, everything seems clear. But the presence of the non-normative contradiction suggests either an editorial problem or an actual defect in the spec.
While the standard itself is clear and normatively reasonable in terms of wording, it appears that this is not what the writers of the standard intended.
P0717 introduced the concept of "functionally equivalent" as being distinct from "equivalent". And that proposal was accepted. However, P0717 was introduced early in the process of adopting the Concepts TS for C++20. In that proposal, it specifically spoke of terse template syntax, and EWG explicitly voted in favor of adopting "functionally equivalent" wording for it instead of the Concepts TS "equivalent" wording.
That is, P0717 makes it clear that the committee intended for users to be required to use consistent syntax.
However, terse template syntax from Concepts TS was removed from C++20 (or rather, never really added). Which meant that any "functionally equivalent" wording never made it in, since the feature never made it in.
Then P1141 happened, which added abbreviated template syntax, that covered much of the ground of the Concepts TS terse template syntax. But, despite one of the authors of P0717 being an author of P1141, apparently someone made a mistake in the wording and nobody caught it. This would explain why the non-normative text calls out the lack of true equivalence: because that was actually the committee's intent.
So it's very possible that this is a mistake in the normative text.
Related
Here is a struct with a templated constructor that is defined out of line:
template <typename T>
struct Foo {
template <typename F>
Foo(F f);
};
template <typename T>
template <typename F>
Foo<T>::Foo(F f) {}
Clang is happy with this under -std=c++20. If I add a requires clause to the templated constructor, it is still happy. But if the requires clause mentions the struct, it is not happy:
#include <concepts>
template <typename T>
struct Foo {
template <typename F>
requires (!std::same_as<Foo<T>, int>)
Foo(F f);
};
template <typename T>
template <typename F>
requires (!std::same_as<Foo<T>, int>)
Foo<T>::Foo(F f) {}
<source>:13:9: error: out-of-line definition of 'Foo<T>' does not match any declaration in 'Foo<T>'
Foo<T>::Foo(F f) {}
^~~
GCC does accept this.
Is clang right to reject it? And if so, what part of the standard says so?
If you're interested why I have a requires clause that references the type being constructed, it's to disambiguate the constructor from the move constructor so that the next requires clause in a larger requires expression won't be evaluated when F is the same as Foo . Otherwise it recursively depends upon itself. The real code is more complicated, and accepts a forwarding reference to F.
I think this could be related to the compiler not being able to deduce what T is. When using a requires clause inside a struct, the compiler needs to be able to instantiate the class template in order to check the requirements specified in the requires clause. This means that all template parameters used within the requires clause must be in scope and the compiler must be able to deduce their types.
If the class template is not fully defined yet, the compiler will not be able to instantiate it, and the code will not be able to compile. Just to be clear, I do not know with certainty, as I have barely used this feature.
As I sort of expected, this seems to be a known clang bug: https://github.com/llvm/llvm-project/issues/49620
All standard references below refers to the current ISO Standard Working Draft, generated on 2020-06-22.
[dcl.fct]/18 states that [extract, emphasis mine]:
An abbreviated function template is a function declaration that has one or more generic parameter type placeholders ([dcl.spec.auto]). An abbreviated function template is equivalent to a function template ([temp.fct]) whose template-parameter-list includes one invented type template-parameter for each generic parameter type placeholder of the function declaration, in order of appearance. [...]
Such that the following to function declarations are areuably equivalent:
template <typename T>
void f(T);
void f(auto); // re-declaration
We may note, however, that the example of [dcl.fct]/18 states that
[...]
These declarations are functionally equivalent (but not equivalent) to the following declarations.
[...]
which may arguably (I'm unsure how interpret this) conflict with the equivalence statement in the prior passage.
Now, both GCC 10.1.0 and Clang 10.0.0 (as well as GCC:HEAD and Clang:HEAD) have some mixed behavior here. If we declare a function template and later define it (/re-declare it) using a mixed classical function template syntax with abbreviated function template syntax, Clang accepts most cases (defining a previously declared function) whereas GCC rejects all (sees the (attempted) re-declarations as separately declared functions with subsequent ambiguity failures in overload resolution):
// A1: Clang OK, GCC error
template <typename T>
void a(T);
void a(auto) {}
// B1: Clang OK, GCC error
void b(auto);
template <typename T>
void b(T) {}
// C1: Clang OK, GCC error
template <typename T, typename U>
void c(T, U);
void c(auto, auto) {}
// D1: Clang OK, GCC error
template <typename T, typename U>
void d(T, U);
template <typename T>
void d(T, auto) {}
// E1: Clang error, GCC error
template <typename T>
void e(T, auto);
template <typename T>
void e(auto, T) {}
int main() {
a(0); // Clang OK, GCC error.
b(0); // Clang OK, GCC error.
c(0, '0'); // Clang OK, GCC error.
d(0, '0'); // Clang OK, GCC error.
e(0, '0'); // Clang error, GCC error.
}
Curiously, if we make the function template a class member function template, both GCC and Clang accepts cases A1 through D1, but both rejects the final case E1 above:
// A2: OK
struct Sa {
template <typename T>
void a(T);
};
void Sa::a(auto) {}
// B2: OK
struct Sb {
void b(auto);
};
template <typename T>
void Sb::b(T) {}
// C2: OK
struct Sc {
template <typename T, typename U>
void c(T, U);
};
void Sc::c(auto, auto) {}
// D2: OK
struct Sd {
template <typename T, typename U>
void d(T, U);
};
template <typename T>
void Sd::d(T, auto) {}
// E2: Error
struct Se {
template <typename T>
void e(T, auto);
};
template <typename T>
void Se::e(auto, T) {}
with the following error messages:
GCC
error: no declaration matches 'void Se::e(auto:7, T)'
note: candidate is:
'template<class T, class auto:6> void Se::e(T, auto:6)'
Clang
error: out-of-line definition of 'e' does not match
any declaration in 'Se'
Now, the name of a type template parameter is not required to be consistent over re-declaration (or a definition) of a function template, as just names a generic type placeholder.
GCC's error message is particularly interesting, hinting that invented type template parameters are treated as concrete types rather than generic type placeholders.
Question:
Which of GCC and Clang are correct regarding cases A1 through D1 (rejecting and accepting, respectively)? Are GCC and Clang correct to reject case E2 above? What standard passage (of the working draft) unambiguously supports them?
This:
template <typename T>
void e(T, auto);
Translates to this:
template<typename T, typename U>
void e(T, U);
By contrast, this:
template <typename T>
void e(auto, T) {}
translates to:
template <typename T, typename U>
void e(U, T) {}
Remember that abbreviated function template parameters are placed at the end of the template parameter list. So those aren't declaring the same template, due to reversing the order of the template parameters. The first one declares one template, the second one declares and defines a different template.
You don't get a compile error just from this because the second definition is also a declaration. However, when you use a class member, out-of-member definitions are not declarations. Therefore, they must have a matching in-member declaration. Which they don't; hence the errors.
As for the others, the "functionally equivalent (but not equivalent)" text is a non-normative notation. The actual normative text that you quoted clearly states that these are "equivalent", not merely "functionally equivalent". And since the term "equivalent", per [temp.over.link]/7, is used to match declarations and definitions, it seems to me that the standard states that the A through D cases are fine.
What's weird is that this non-normative text was introduced by the same proposal that introduced the normative text. However, the proposal that it inherited the ConceptName auto syntax from seems clear that it means "equivalent", not "functionally equivalent".
So in terms of normative text, everything seems clear. But the presence of the non-normative contradiction suggests either an editorial problem or an actual defect in the spec.
While the standard itself is clear and normatively reasonable in terms of wording, it appears that this is not what the writers of the standard intended.
P0717 introduced the concept of "functionally equivalent" as being distinct from "equivalent". And that proposal was accepted. However, P0717 was introduced early in the process of adopting the Concepts TS for C++20. In that proposal, it specifically spoke of terse template syntax, and EWG explicitly voted in favor of adopting "functionally equivalent" wording for it instead of the Concepts TS "equivalent" wording.
That is, P0717 makes it clear that the committee intended for users to be required to use consistent syntax.
However, terse template syntax from Concepts TS was removed from C++20 (or rather, never really added). Which meant that any "functionally equivalent" wording never made it in, since the feature never made it in.
Then P1141 happened, which added abbreviated template syntax, that covered much of the ground of the Concepts TS terse template syntax. But, despite one of the authors of P0717 being an author of P1141, apparently someone made a mistake in the wording and nobody caught it. This would explain why the non-normative text calls out the lack of true equivalence: because that was actually the committee's intent.
So it's very possible that this is a mistake in the normative text.
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()
{
}
This question already has an answer here:
Fallback variadic constructor - why does this work?
(1 answer)
Closed 5 years ago.
Consider this program:
#include <iostream>
#include <type_traits>
using namespace std;
struct russell {
template <typename barber,
typename = typename enable_if<!is_convertible<barber, russell>::value>::type>
russell(barber) {}
};
russell verify1() { return 42L; }
russell verify2() { return 42; }
int main ()
{
verify1();
verify2();
cout << is_convertible<long, russell>::value;
cout << is_convertible<int, russell>::value;
return 0;
}
If some type barber is not convertible to russell. we attempt to create a paradox by making it convertible (enabling a converting constructor).
The output is 00 with three popular compilers, though constructors are evidently working.
I suspect the behaviour should be undefined, but cannot find anything in the standard.
What should the output of this program be, and why?
During overload resolution, template argument deduction must instantiate the default argument to obtain a complete set of template arguments to instantiate the function template with (if possible). Hence the instantiation of is_convertible<int, russell> is necessitated, which internally invokes overload resolution. The constructor template in russell is in scope in the instantiation context of the default template argument.
The crux is that is_convertible<int, russell>::value evaluates the default template argument of russell, which itself names is_convertible<int, russell>::value.
is_convertible<int, russell>::value
|
v
russell:russell(barber)
|
v
is_convertible<int, russell>::value (not in scope)
core issue 287's (unadopted) resolution seems to be the de facto rule abode by major compilers. Because the point of instantiation comes right before an entity, value's declaration is not in scope while we're evaluating its initialiser; hence our constructor has a substitution failure and is_convertible in main yields false.
Issue 287 clarifies which declarations are in scope, and which are not, namely value.
Clang and GCC do slightly differ on how they treat this situation. Take this example with a custom, transparent implementation of the trait:
#include <type_traits>
template <typename T, typename U>
struct is_convertible
{
static void g(U);
template <typename From>
static decltype(g(std::declval<From>()), std::true_type{}) f(int);
template <typename>
static std::false_type f(...);
static const bool value = decltype(f<T>()){};
};
struct russell
{
template <typename barber,
typename = std::enable_if_t<!is_convertible<barber, russell>::value>>
russell(barber) {}
};
russell foo() { return 42; }
int main() {}
Clang translates this silently. GCC complains about an infinite recursion chain: it seems to argue that value is indeed in scope in the recursive instantiation of the default argument, and so proceeds to instantiate the initializer of value again and again. However, arguably Clang is in the right, since both the current and the drafted relevant phrase in [temp.point]/4 mandate that the PoI is before the nearest enclosing declaration. I.e. that very declaration is not considered to be part of the partial instantiation (yet). Kinda makes sense if you consider the above scenario. Workaround for GCC: employ a declaration form in which the name is not declared until after the initializer is instantiated.
enum {value = decltype(f<T>()){}};
This compiles with GCC as well.
I have been experimenting with C++ concepts recently. I am trying the definitions from the following Ranges Extensions document:
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/n4569.pdf
The definitions and usages of Same are confusing me. For reasons unknown to me, the authors did not give an explicit definition. So I am using:
template <class T, class U>
concept bool Same()
{
return std::is_same<T, U>::value;
}
The problem is that the document gives the following definition for Assignable:
template <class T, class U>
concept bool Assignable()
{
return Common<T, U>() && requires(T&& a, U&& b) {
{ std::forward<T>(a) = std::forward<U>(b) } -> Same<T&>;
};
}
It does not work (under GCC 6.3): a simple Assignable<int&, int&&>() concept check gives me false (I have verified that the Common part is OK). I have to change Same<T&> to T& to make it seemingly work. The same Same<Type> check is used in some other places too.
My questions are:
Is my definition of Same correct?
Why is Same<T&> used instead of T&? What are the differences?
Thanks for any help.
After attacking the problem during the weekend, I think I have found the answer myself.
Eric Niebler and Casey Carter have a more refined definition of Same that supports multiple template arguments (not just two), but my definition should be basically right for the two-argument case.
When using -> Type, the purpose is that the expression in the brackets can be implicitly converted to Type. When using -> Same<Type>, the purpose is that the expression in the brackets is exactly Type. So they are different.
However, there is a gotcha. The constraint check is quite complicated, and even experts like Eric and Casey made a mistake and gave wrong definitions in N4569. Eric discussed the issue on GitHub:
https://github.com/ericniebler/stl2/issues/330
When used the way it was given in N4569, it means the expression should be able to be passed to an imagined function template like
template <typename U>
f(U)
requires Same<T&, U>()
This doesn't work—if the expression passed in is an lvalue of T, the deduced U is T instead of T&. The solution is use Same<T&>&& in Assignable. It will result in the following imagined function template:
template <typename U>
f(U&&)
requires Same<T&, U>()
Now everything is OK—if the expression passed in is an lvalue of T, U has to be deduced as T&.
Playing with concepts is a good practice for me, but I probably should find their code earlier. They have a complete set of concepts in the following GitHub repository:
https://github.com/CaseyCarter/cmcstl2
People interested in C++ concepts should look into it.
The issue with the definition of Same is a bit more subtle: the Ranges TS requires implementations to treat the constraint Same<T, U>() as equivalent to Same<U, T>(), i.e., recognize the symmetry of "T is the same type as U":
template <class T, class U>
requires Same<T, U>()
void foo(); // #1
template <class T, class U>
requires Same<U, T>()
void foo(); // redeclaration of #1
template <class T>
requires Same<int, T>()
void bar(); // #2
template <class T>
requires Same<T, int>()
void bar(); // redeclaration of #2
This equivalence can't be expressed in the language, since the rules for normalization of constraints recognize each of:
is_same_v<T, U>
is_same_v<U, T>
is_same_v<T, int>
is_same_v<int, T>
as distinct atomic constraints. This requires implementation of Same via a compiler intrinsic.