3
votes

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?

4
Sounds like you might be looking for std::is_constructibleNathanOliver
@NathanOliver: Could you be conflating template instantiation with object construction?Andrew Tomazos
I don't think there is a way to detect hard errors in instantiation. So if A is not SFINAE friendly, I don't think you can have your WorksWithA automatically.Jarod42
@rustyx: Right, if the definition of A is modified I want WorksWithA (and any associated machinery) to continue to give the right answer without modification.Andrew Tomazos
@newbie instantiation of A yields a type, object construction constructs an object463035818_is_not_a_number

4 Answers

0
votes

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

0
votes

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.

0
votes

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?

-3
votes

You can use a simple concept:

template<typename T>
constexpr bool WorksWithA = requires { typename A<T>; };