问题
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 ofT&
? What are the differences?
Thanks for any help.
回答1:
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.
回答2:
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.
来源:https://stackoverflow.com/questions/42870265/c-concepts-same-and-assignable