Why do some of these implementations of a "Oneable" concept using requires expressions in C++ 20 not compile on certain compilers?
// `Oneable` implemented using `requires` with a simple assignment expression
// clang, gcc, and msvc all compile
template <class T>
concept Oneable = requires(T n) { n = 1; };
static_assert(Oneable<int>);
static_assert(!Oneable<int*>);
// `Oneable` implemented using `requires` with an assignment statement inside
// the body of a lambda expression
// clang and msvc compile
// gcc gives a compiler error on instantiating Oneable<T*>
template <class T>
concept Oneable = requires { []() { T n = 1; }; };
static_assert(Oneable<int>);
static_assert(!Oneable<int*>);
// `Oneable` implemented using `requires` with an instantiation of a struct
// gcc and msvc compile
// clang gives a compiler error on instantiating Oneable<int*>
template <class T>
struct S { T n = 1; };
template <class T>
concept Oneable = requires { S<T>{}; };
static_assert(Oneable<int>);
static_assert(!Oneable<int*>);
gcc's output for the second snippet:
<source>: In lambda function:
<source>:8:43: error: invalid conversion from 'int' to 'int*' [-fpermissive]
8 | concept Oneable = requires { []() { T n = 1; }; };
| ^
| |
|
clang's output for the third snippet:
<source>:8:18: error: cannot initialize a member subobject of type 'int *' with an rvalue of type 'int'
struct S { T n = 1; };
^
<source>:11:35: note: in instantiation of default member initializer 'S<int *>::n' requested here
concept Oneable = requires { S<T>{}; };
^
<source>:11:30: note: in instantiation of requirement here
concept Oneable = requires { S<T>{}; };
^~~~~~
<source>:11:19: note: while substituting template arguments into constraint expression here
concept Oneable = requires { S<T>{}; };
^~~~~~~~~~~~~~~~~~~~
<source>:15:18: note: while checking the satisfaction of concept 'Oneable<int *>' requested here
std::cout << Oneable<int*> << '\n';
^~~~~~~~~~~~~
The compilers used are x86-64 gcc 12.2, x86-64 clang 15.0.0, and x64 msvc 19.33.
No flags are given other than those required to set the language standard.
The issue here has to do with immediate context. The technology at play here is SFINAE - substitution failure is not an error. But this is only not an error in what's called the immediate context of the substitution - a term which is basically not really well defined, unfortunately.
Basically, though, the actual signature of a function template is the immediate context, but the body of a function template is not. Anything that goes wrong in a function body is a hard compiler error, not a gracefully-checkable one. So this version:
template <class T>
concept Oneable = requires { []() { T n = 1; }; };
is not going to work - the failure for T=int* happens in the lambda body. That is outside of the immediate context of the expression.
This version:
template <class T>
struct S { T n = 1; };
template <class T>
concept Oneable = requires { S<T>{}; };
is hard to provide a clear answer for since, again, immediate context is not well specified. This one probably should be made to work (in a way that the lambda case never will be), and gcc for instance just makes default member initializers part of the immediate context since that's the most user-friendly option. Otherwise you end up with like:
template <typename T>
struct A {
T var = T();
A() = default;
};
template <typename T>
struct B {
T var = T();
B() requires std::default_initializable<T> = default;
};
B is obviously correct, but it's kind of a ridiculous thing to have to write, so it'd be nice if A were correct also.
Best thing to do right now if you want to be reliable across all compilers is to try to avoid ambiguous situations like this. Write code like B, not like A.
Related
It appears that you can put lambda in the concept and then write code in it. Let us take this as an example. I'll prefer the standard concepts for such concepts and bear in mind that this is only for purposes of this example - godbolt
template<class T>
concept labdified_concept =
requires {
[](){
T t, tt; // default constructible
T ttt{t}; // copy constructible
tt = t; //copy assignable
tt = std::move(t); // move assignable
};
};
Instead of:
template<class T>
concept normal_concept =
std::default_initializable<T> && std::movable<T> && std::copy_constructible<T>;
Is lambdification an improvement or bad practice? From readability point too.
This shouldn't be valid. The point of allowed lambdas into unevaluated contexts wasn't to suddenly allow SFINAE on statements.
We do have some wording in [temp.deduct]/9 that makes this clear:
A lambda-expression appearing in a function type or a template parameter is not considered part of the immediate context for the purposes of template argument deduction. [Note: The intent is to avoid requiring implementations to deal with substitution failure involving arbitrary statements. [Example:
template <class T>
auto f(T) -> decltype([]() { T::invalid; } ());
void f(...);
f(0); // error: invalid expression not part of the immediate context
template <class T, std::size_t = sizeof([]() { T::invalid; })>
void g(T);
void g(...);
g(0); // error: invalid expression not part of the immediate context
template <class T>
auto h(T) -> decltype([x = T::invalid]() { });
void h(...);
h(0); // error: invalid expression not part of the immediate context
template <class T>
auto i(T) -> decltype([]() -> typename T::invalid { });
void i(...);
i(0); // error: invalid expression not part of the immediate context
template <class T>
auto j(T t) -> decltype([](auto x) -> decltype(x.invalid) { } (t)); // #1
void j(...); // #2
j(0); // deduction fails on #1, calls #2
— end example] — end note]
We just don't have something equivalent for requirements. gcc's behavior is really what you'd expect:
template <typename T> concept C = requires { []{ T t; }; };
struct X { X(int); };
static_assert(!C<X>); // ill-formed
Because the body of the lambda is outside of the immediate context, so it's not a substitution failure, it's a hard error.
Ignoring the obvious readability flaws in this mechanism, it doesn't actually work. Consider the following:
template<labdified_concept T>
void foo(T t) {}
template<typename T>
void foo(T t) {}
The rules of concepts tell us that if a given T doesn't satisfy labdified_concept, then the other foo should be instantiated instead. But that's not what happens if we provide SS to such a template. Instead, we get a hard error because labdified_concept<SS> cannot be instantiated.
The stuff within a requires expression has special handling that allows certain types of errors to be regarded as failures to meet the requirement. But that handling doesn't apply to the body of a lambda. There, ill-formed code is ill-formed and thus you get a compile error when trying to instantiate it.
And even if it did work, it still doesn't work. Concepts have complex rules for subsumption of concepts, which allows different concepts to be considered more highly specialized than others. This allows overloading on different concepts, which lets the more constrained concept get called. For example a concept that only requires default_initializable is more generic than one which requires default_initializable and moveable. Thus, if a type fulfills both, the latter will be taken because it is more constrained.
But this only works because of the special rules for concepts. Hiding requirements in lambdas wouldn't allow this to work.
It appears that you can put lambda in the concept and then write code in it. Let us take this as an example. I'll prefer the standard concepts for such concepts and bear in mind that this is only for purposes of this example - godbolt
template<class T>
concept labdified_concept =
requires {
[](){
T t, tt; // default constructible
T ttt{t}; // copy constructible
tt = t; //copy assignable
tt = std::move(t); // move assignable
};
};
Instead of:
template<class T>
concept normal_concept =
std::default_initializable<T> && std::movable<T> && std::copy_constructible<T>;
Is lambdification an improvement or bad practice? From readability point too.
This shouldn't be valid. The point of allowed lambdas into unevaluated contexts wasn't to suddenly allow SFINAE on statements.
We do have some wording in [temp.deduct]/9 that makes this clear:
A lambda-expression appearing in a function type or a template parameter is not considered part of the immediate context for the purposes of template argument deduction. [Note: The intent is to avoid requiring implementations to deal with substitution failure involving arbitrary statements. [Example:
template <class T>
auto f(T) -> decltype([]() { T::invalid; } ());
void f(...);
f(0); // error: invalid expression not part of the immediate context
template <class T, std::size_t = sizeof([]() { T::invalid; })>
void g(T);
void g(...);
g(0); // error: invalid expression not part of the immediate context
template <class T>
auto h(T) -> decltype([x = T::invalid]() { });
void h(...);
h(0); // error: invalid expression not part of the immediate context
template <class T>
auto i(T) -> decltype([]() -> typename T::invalid { });
void i(...);
i(0); // error: invalid expression not part of the immediate context
template <class T>
auto j(T t) -> decltype([](auto x) -> decltype(x.invalid) { } (t)); // #1
void j(...); // #2
j(0); // deduction fails on #1, calls #2
— end example] — end note]
We just don't have something equivalent for requirements. gcc's behavior is really what you'd expect:
template <typename T> concept C = requires { []{ T t; }; };
struct X { X(int); };
static_assert(!C<X>); // ill-formed
Because the body of the lambda is outside of the immediate context, so it's not a substitution failure, it's a hard error.
Ignoring the obvious readability flaws in this mechanism, it doesn't actually work. Consider the following:
template<labdified_concept T>
void foo(T t) {}
template<typename T>
void foo(T t) {}
The rules of concepts tell us that if a given T doesn't satisfy labdified_concept, then the other foo should be instantiated instead. But that's not what happens if we provide SS to such a template. Instead, we get a hard error because labdified_concept<SS> cannot be instantiated.
The stuff within a requires expression has special handling that allows certain types of errors to be regarded as failures to meet the requirement. But that handling doesn't apply to the body of a lambda. There, ill-formed code is ill-formed and thus you get a compile error when trying to instantiate it.
And even if it did work, it still doesn't work. Concepts have complex rules for subsumption of concepts, which allows different concepts to be considered more highly specialized than others. This allows overloading on different concepts, which lets the more constrained concept get called. For example a concept that only requires default_initializable is more generic than one which requires default_initializable and moveable. Thus, if a type fulfills both, the latter will be taken because it is more constrained.
But this only works because of the special rules for concepts. Hiding requirements in lambdas wouldn't allow this to work.
I have a class that looks similar to this:
template <class T = char>
struct C {
T value;
};
Up to C++14, when I wanted to use it with the default template argument I always had to specify empty angle brackets:
void f() {
C<> c;
c.value = 'x';
}
Since C++17 supports class template argument deduction and explicit deduction guides, I wonder if there is a way now to make the above code work without specifying the empty angle brackets:
void f() {
C c;
c.value = 'x';
}
This code worked in GCC 8.0 if I compile it with -std=gnu++17. However, it still showed an error in Clang 6.0 and Visual Studio 15.7. Which compiler is correct in this situation?
I also tried specifying a deduction guide like this:
C() -> C<char>;
This also didn't help. Is that the correct syntax or is there even a way to specify deduction guides for a default constructor?
This program is correct:
template <class T = char>
struct C {
T value;
};
int main() {
C c;
c.value = 'x';
}
clang just doesn't fully support class template argument deduction yet (note that it does compile on trunk).
Class template argument deduction will try to perform overload resolution on the candidate set:
template <class T=char> auto __f() -> C<T>
template <class T=char> auto __f(C<T>) -> C<T>
with no initializer. That first one is a viable candidate (deducing T as char), the second one is not, so the first one is trivially the best viable candidate. So we end up with c having type C<char>. When we then do overload resolution on default construction, that works, so the program is fine.
I have a problem with detecting when an instantiation of a generic lambda is well formed but not compilable, and detecting it has stumped me:
#include <functional>
class future
{
public:
int get() & { return 5; }
};
// Gets the return type of F(A), returning a not_well_formed type if not well formed
template<class F, class A> struct get_return_type
{
struct not_well_formed {};
template<class _F, class _A> static not_well_formed test(...);
template<class _F, class _A> static auto test(_F &&f) noexcept(noexcept(f(std::declval<_A>()))) -> decltype(f(std::declval<_A>()));
using type = decltype(test<F, A>(std::declval<F>()));
static constexpr bool is_noexcept = noexcept(test<F, A>(std::declval<F>()));
};
int main(void)
{
auto foo=[](auto &&x) { return x.get(); };
using type=get_return_type<decltype(foo), const future>::type;
return 0;
}
This fails with (on clang 3.7):
ned#kate:~$ clang++-3.7 -std=c++14 -o weird_generic_lambda_thing weird_generic_lambda_thing.cpp
weird_generic_lambda_thing.cpp:21:34: error: member function 'get' not viable: 'this' argument has type 'const future', but
function is not marked const
auto foo=[](auto &&x) { return x.get(); };
^
weird_generic_lambda_thing.cpp:14:111: note: in instantiation of function template specialization 'main()::(anonymous
class)::operator()<const future>' requested here
..._F, class _A> static auto test(_F &&f) noexcept(noexcept(f(std::declval<_A>()))) -> decltype(f(std::declval<_A>()));
^
weird_generic_lambda_thing.cpp:15:25: note: while substituting explicitly-specified template arguments into function
template 'test'
using type = decltype(test<F, A>(std::declval<F>()));
^
weird_generic_lambda_thing.cpp:22:14: note: in instantiation of template class 'get_return_type<(lambda at
weird_generic_lambda_thing.cpp:21:12), const future>' requested here
using type=get_return_type<decltype(foo), const future>::type;
^
weird_generic_lambda_thing.cpp:6:7: note: 'get' declared here
int get() & { return 5; }
^
1 error generated.
You can probably blame my inexperience with Expression SFINAE here (thanks Visual Studio!), but I am surprised: surely the decltype creating the return type of test() should fail to substitute if f(std::declval<_A>()) is not well formed?
Obviously the answer is that is does fail to substitute, but in a non-SFINAE way. Can the above be fixed so it correctly returns a not_well_formed type if the generic lambda is uncompilable with some arbitrary parameter type?
You cannot in general. Only early failures can be detected via SFINAE. Early failures are basically the declaration, not the definition, of a function (or class) template.
The lambda can provide SFINAE early failure instrumentation via explicitly declaring the return type ->decltype(x.get()), or through other SFINAE techniques like enable_if_t or void_t.
The idea is that compilers are not required to fully compile functions in order to engage in overload resolution.
In the case of a substitution failure involving a template alias (e.g. a template alias on a missing member typename, as in the code snippet below), should an error be triggered ?
Clang and gcc seem to disagree on this:
// some types
struct bar { };
struct foo {
typedef void member_type;
};
// template alias
template<class T>
using member = typename T::member_type;
template<class T>
void baz(... ) { }
// only works for gcc, clang fails with: no type named 'member_type'
// in 'bar'
template<class T>
void baz( member<T>* ) { }
int main(int, char** ) {
baz<bar>(0); // picks first
baz<foo>(0); // picks second
return 0;
}
So the question is: who is correct, and why ?
Thanks :-)
According to the Standards, it is clearly GCC that is correct, because the alias template must immediately be replaced and then normal/usual SFINAE is applied to typename T::member_type later when T is known.
But there currently is an issue for this, see http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#1554.
According to the meetings outcome, it appears that clangs behavior is desired: The substitution of T will be done in the context of the alias template (even though at the time of substitution in typename T::member_type, there is no reference to the alias template anymore - it will still need to be referenced as the source of where the parameter type pattern originated from, if this is how it's implemented).
This is similar to another situation where patterns are thrown away on definition time that could influence instantiation semantics
template<int I>
void f(int x[I]);
int main() {
f<0>(nullptr);
}
In this case too, in my opinion the Standard normatively is clear that the parameter is immediately being replaced by int* and thus the instantiation works. See http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#1322 .