问题
Let's say I have a class template A that has one type template parameter, and a single primary specialization:
template<typename T> struct A {
/*...*/
};
For some T
arguments A<T>
will instantiate successfully, for others it won't.
Without modifying or extending the definition of A
, is it possible to write a bool variable template:
template<typename T>
constexpr bool WorksWithA = /*...*/;
such that WorkWithA<T>
is true iff A<T>
would instantiate successfully?
Update
Posted this as separate question: Using typename in C++20 requires / concept?
#include <iostream>
template<typename T>
struct A {
using X = typename T::X;
};
template<typename T>
constexpr bool WorksWithA = requires { typename A<T>; };
struct GoodArg {
using X = int;
};
struct BadArg {
};
int main() {
std::cout << WorksWithA<GoodArg> << std::endl;
std::cout << WorksWithA<BadArg> << std::endl;
}
Compiled and run with:
$ clang++ --version
clang version 10.0.0-4ubuntu1
$ clang++ test.cc -std=c++20
$ ./a.out
1
1
This outputs 1 1
, expected output is 1 0
?
What gives?
回答1:
If I'm understanding this correctly, for SFINAE friendly types, this could be a way:
Match if instantiation is possible:
template<class T>
constexpr auto WorksWithA(int) -> A<T>;
Match if it isn't:
struct no {};
template<class T>
constexpr auto WorksWithA(long) -> no;
Helper:
template<typename T>
inline constexpr bool WorksWithA_v =
not std::is_same_v<no, decltype(WorksWithA<T>(0))>;
Demo
If A
isn't SFINAE friendly (as the A
in the updated question), you'll have to add the check in WorksWithA
. In this case:
template<typename T>
struct A {
using X = typename T::X;
};
template<class T>
constexpr auto WorksWithA(int) -> typename T::X;
struct no {};
template<class T>
constexpr auto WorksWithA(long) -> no;
template<typename T>
inline constexpr bool WorksWithA_v =
not std::is_same_v<no, decltype(WorksWithA<T>(0))>;
Demo
回答2:
You misunderstand how templates work.
template<typename T>
struct A {
using X = typename T::X;
};
template<typename T>
constexpr bool WorksWithA = requires { typename A<T>; };
struct GoodArg {
using X = int;
};
struct BadArg {
};
BadArg
fundamentally works with A. It isn't bad.
template<typename T>
struct A {
using X = typename T::X;
};
this does not require that T
have a typename X
.
This states that if it does have a typename X
, then A::X
is that typename.
So WorksWithA
does test correctly if T
can instantiate the template A
.
This doesn't help you directly, but understanding that you are asking the wrong question is important, and why your question is the wrong one. Because that leads to the right question.
Either you want A
not not instantiate when T
does not have a type X
, or you want WorksWithA
to do a different kind of test.
For example, maybe you want HasAnX
:
template<class T>
concept HasAnX = requires { typename T::X; };
then
static_assert(HasAnX<GoodArg>);
static_assert(!HasAnX<BadArg>);
compiles.
However, I'd guess that semantically this doesn't match what you want it to match. You want it to be the A
-ness not the X
-ness you are measuring.
Then your problem is probably not with WorksWithA
, but with A
itself. You thought you where adding a requirement that T
HasAnX
, but you where not.
template<HasAnX T>
struct A {
using X = typename T::X;
};
that makes A
require that T
has an X
. Now, WorksWithA
... works as you intended.
Live example.
回答3:
This is a good (and right) question, but I think as up until now there is no answer for this question in general.
Lets assume the class template A
is simply like:
template<typename T>
class A{
public:
T variable;
}
This template works with most of types but it does not instantiate with T = void
for the obvious reason. now lets assume we want to find that out before hand with a SFINAE friendly method.
The best tool to find out if a type is instantiatable is to check whether it has a size, now Lets define a tool similar to std::void_t
:
template<size_t...> using void_size_t = void;
now if we define:
template< class, class = void >
struct pre_WorksWithA : std::false_type { };
template< class T >
struct pre_WorksWithA<T, void_size_t<sizeof(A<T>)>> : std::true_type { };
template<typename T>
constexpr bool WorksWithA = pre_WorksWithA<T>::value;
In case of a normal types like int
the result is true
which it should be. but in case of void
neither the WorksWithA
nor pre_WorksWithA
can instantiate and the result would be an error from the compiler.
So as you can see, up until now, basically there is no feature in C++ to help us know whether a type is instantiate-able or not without giving an error?
回答4:
You can use a simple concept:
template<typename T>
constexpr bool WorksWithA = requires { typename A<T>; };
来源:https://stackoverflow.com/questions/66208977/a-variable-template-that-is-true-iff-a-class-template-would-instantiate