问题
Recently I was using concept
s to define different constructors for a template
d struct
. Here is the code:
#include <iostream>
namespace detail {
template<typename T, typename U >
concept SameHelper = std::is_same_v<T, U>;
}
template<typename T, typename U >
concept same_as = detail::SameHelper<T, U> && detail::SameHelper<U, T>;
template<typename T>
concept trivial = same_as<T, bool> || same_as<T, char>;
template<typename T>
concept not_trivial = !trivial<T>;
template<typename T>
struct Foo
{
Foo(T t) requires trivial<T> : member{t} { std::cout << "Foo is trivial" <<std::endl; }
Foo(const auto& t) requires not_trivial<T> : member{t} { std::cout << "Foo is not trivial" <<std::endl;}
const T member;
};
template<typename T>
struct Bar
{
Bar(auto t) requires trivial<T> : member{t} { std::cout << "Bar is trivial" <<std::endl; }
Bar(const T& t) requires not_trivial<T> : member{t} { std::cout << "Bar is not trivial" <<std::endl;}
const T member;
};
template<typename T>
struct Baz
{
Baz(auto t) requires trivial<T> : member{t} { std::cout << "Baz is trivial" <<std::endl; }
Baz(const auto& t) requires not_trivial<T> : member{t} { std::cout << "Baz is not trivial" <<std::endl;}
const T member;
};
template<typename T>
struct Qux
{
Qux(T t) requires trivial<T> : member{t} { std::cout << "Qux is trivial" <<std::endl; }
Qux(const T& t) requires not_trivial<T> : member{t} { std::cout << "Qux is not trivial" <<std::endl;}
const T member;
};
int main()
{
Foo(true);
Foo(3.14159);
Bar(true);
Bar(3.14159);
//Baz(true); // does not compile if uncommented
//Baz(3.14159); // does not compile if uncommented
//Qux(true); // does not compile if uncommented
//Qux(3.14159); // does not compile if uncommented
return 0;
}
You can run the above code online. I am wondering why Foo and Bar compile fine, whereas Baz and Qux do not compile if uncommented. IMHO the syntax for Baz and Qux is mutch more convenient.
回答1:
Let's go through all the class templates in order. I'm going to use a simpler concept, since bool
is the only relevant type:
template <typename T>
struct Foo
{
Foo(T) requires same_as<T, bool>;
Foo(const auto&) requires (not same_as<T, bool>);
};
Foo(true);
Foo(3.14159);
When doing class template argument deduction, the process is that we take all the constructors and turn them into functions - and then perform overload resolution to figure out what specific type we end up at. For Foo
, these would become:
template <typename T> requires same_as<T, bool>
auto __f(T) -> Foo<T>;
template <typename T> requires (not same_as<T, bool>)
auto __f(const auto&) -> Foo<T>;
__f(true); // from Foo(true)
__f(3.14159); // from Foo(3.14159)
In the first overload of __f
, T
is deducible from its argument. In the second overload, it is not - there is no way to determine what T
is... so it basically doesn't matter, as far as the CTAD process goes. As a result, __f(true)
is fine (you get back Foo<bool>
) but __f(3.14159)
is ill-formed - the first overload isn't viable because double
isn't bool
and the second overload isn't viable because T
isn't deduced.
At least that's what the rules should be. The wording as it exists today is missing the rule that we carry over the constraints from each constructor into the overload set, and clang happens to follow the letter of the rules here - its version of __f
's don't have any requires
attached to them. But this is definitely not what we want to happen here, and will certainly be a Core issue. See also llvm bug #44484.
Bar
is similar, just with the arguments flipped:
template<typename T>
struct Bar
{
Bar(auto) requires same_as<T, bool>;
Bar(const T&) requires (not same_as<T, bool>);
};
Here, the only constructor that could give us an answer for CTAD is the second one - but the second one requires that T
is not bool
. So Bar(true)
is ill-formed, but Bar(3.14159)
is fine and gives you Bar<double>
.
For Baz
:
template<typename T>
struct Baz
{
Baz(auto) requires same_as<T, bool>;
Baz(const auto&) requires (not same_as<T, bool>);
};
now neither constructor participates in CTAD, so you'd have to write a deduction guide yourself in order to do anything here. Rejecting these is correct.
And Qux
:
template<typename T>
struct Qux
{
Qux(T) requires same_as<T, bool>;
Qux(const T&) requires (not same_as<T, bool>);
};
Here, both constructors do participate in CTAD so Qux(true)
and Qux(3.14159)
both work fine (just each picks a different constructor). This is the same kind of behavior as we saw before - clang follows the rules as they are, while gcc (and msvc) follow the rules are they should be.
来源:https://stackoverflow.com/questions/60590608/what-is-the-correct-syntax-for-concept-in-combination-with-a-templated-struct-or