How to detect if a type is one of a list of generic types
Asked Answered
B

2

7

If I have

template <typename T> struct A;
template <typename T> struct B;
template <typename T> struct C;
template <typename T> struct D;

what is the most compact way of testing if some candidate type X is one of them? I'm looking for something like

boost::enable_if< is_instantiation_of_any<X,A,B,C,D> >

but A,B,C and D are templates so I'm not sure how to construct the above.

Bitartrate answered 1/3, 2021 at 12:34 Comment(4)
X,A,B,C,D is a list of templates, not a list of types. You want templates, right? (ie its rather a std::is_instantiation_of_any rather than is_any)Perturbation
yes the name is a better fit.Bitartrate
@bradgonesurfing, is C++11 the max standard you're looking for? If not, I've posted an answer for C++14/17, and you could add another tag to the question.Tripos
Yes it's part of customer API and our limit is C++11 at the moment. It's still a useful answer so leave it there but the question has been answered also with a C++11 answer.Bitartrate
P
9

Not sure if there exists a std::is_instantiation_of, though if all templates have same number of parameters, it is straightforward to implement it (if they don't it is more complicated). To check if a type is an instantiation of any of given templates you just need to fold it (requires C++17):

#include<iostream>
#include<type_traits>


template <typename T> struct A;
template <typename T> struct B;
template <typename T> struct C;
template <typename T> struct D;


template <typename T,template<typename> typename X>
struct is_instantiation_of : std::false_type {};

template <typename A,template<typename> typename X>
struct is_instantiation_of<X<A>,X> : std::true_type {};

template <typename T,template<typename> typename...X>
struct is_instantiation_of_any {
    static const bool value = ( ... || is_instantiation_of<T,X>::value);
};

int main(){
    std::cout << is_instantiation_of< A<int>, A>::value;
    std::cout << is_instantiation_of< A<double>, B>::value;
    std::cout << is_instantiation_of_any< A<int>,A,B>::value;
}

Output:

101

To get a C++11 compliant solution, we can use this neat trick from one of Jarod42s answers:

template <bool ... Bs>
using meta_bool_and = std::is_same<std::integer_sequence<bool, true, Bs...>,
                                   std::integer_sequence<bool, Bs..., true>>;

Its rather clever, true,a,b,c and a,b,c,true are only the same when a, b and c are all true. std::integer_sequence is C++14, but all we need here is a type that has the bools as part of its definition:

namespace my {
    template <typename T,T ...t>
    struct integer_sequence {};
}

Using that we can rewrite the above to:

template <bool ... Bs>
using my_all = std::is_same<my::integer_sequence<bool, true, Bs...>,
                            my::integer_sequence<bool, Bs..., true>>;

And as "ANY(a,b,c,d,...)" is just "! ALL( !a, !b, !c, !d,...)" we can use:

template <bool ... Bs>
struct my_any { static constexpr bool value = ! my_all< ! Bs...>::value; };

to write is_instantiation_of_any in a C++11 friendly way:

template <typename T,template<typename> typename...X>
struct is_instantiation_of_any {
    static const bool value = my_any< is_instantiation_of<T,X>::value ...>::value;
};

Complete C++11 example

Perturbation answered 1/3, 2021 at 12:46 Comment(10)
Now make it work for a template with arbitrary number of type and non-type arguments :DEuphrosyne
@AyxanHaqverdili please don't tease me, my break was already too long ;)Perturbation
These template questions always snipe me too xkcd.com/356Euphrosyne
Tagged C++11, so fold expression should be replaced. (there is a trick for all_of to avoid recursion, any_of is just the negation of none_of (which is all_of with negated condition)).Intoxicative
@AyxanHaqverdili Made it work with abitrary number of type and non-type arguments. godbolt.org/z/rTqqca ( random insertion of ... till it worked :) )Bitartrate
I lie! It works with an abitrary number of type arguments. :( but not with non type arguments. I think that would require an exponential number of overloads ???Bitartrate
@Bitartrate it's not generic enough if it doesn't work with an arbitrary number of type and non-type parameters in an arbitrary order. (looks like you sniped yourself with that too)Euphrosyne
@Bitartrate not sure how serious Ayxans comment was, the thing is that "arbitrary number of mixed type and non-type parameters" is not just x2 or x3 more complex than what I presented in my answer but its a whole different story.Perturbation
C++ 17 adds auto keyword to templates so we can do perfect forwarding. godbolt.org/z/bsMxWe seems close to a solution but I can't quite nail it.Bitartrate
My mistake. auto is only for non-type parameters. It doesn't mean auto type and non type parametersBitartrate
T
3

With C++17 (or C++14 if you edit as per the comments), you can use boost::hana::any_of as a helper:

#include <iostream>
#include <boost/hana/any_of.hpp>
#include <boost/hana/tuple.hpp>
#include <type_traits>

namespace hana = boost::hana;

template <typename T> struct A {};
template <typename T> struct B {};
template <typename T> struct C {};
template <typename T> struct D {};
template <typename T> struct N {};

template<typename T>
bool fun(T) {      // overload for non-templated arguments (templated arguments
    return false;  // are a better match for the overload below)
}

template<typename T, template<typename> typename X>
bool fun(X<T>) { // overload for templated arguments
    auto constexpr pred = [](auto x){ // remove constexpr for < c++17
        return std::is_same_v<decltype(x), X<T>>;
        // return std::is_same<decltype(x), X<T>>::value; // for < c++17
    };
    return hana::any_of(hana::tuple<A<T>, B<T>, C<T>, D<T>>{}, pred);
};

int main() {
    A<int> a{};
    N<int> b{};
    int x = 3; fun(x);
    std::cout << fun(a) << fun(b) << fun(x) << std::endl; // prints 100
}

Probably you want to use std::decay_t before passing things to std::is_same_v.

Tripos answered 2/3, 2021 at 9:30 Comment(9)
this is interesting, but if I understand correctly it fails for eg int x; fun(x);, no? In other words, it only works for types that are instantiations of a template with a single parameterPerturbation
@largest_prime_is_463035818, yes it fails in that case. But if one wants to check if a variable is of a non template class (or of a specific instatiation of a template class), then std::is_same_v does the job, no?Tripos
yes std::is_same_v can help, but its part of the question as I understood it. OP wants to test any type X for being an instantiation of A,B,C or DPerturbation
I don't know, the question reads but A,B,C and D are templates, so I thought that the OP wanted an equivalent of std::is_same_v which checks equality of the "outermost template" rather than that of the concrete types.Tripos
X is not necessarily a templatePerturbation
@largest_prime_is_463035818, oh, ok, I understand what you mean now. Well, it was quick. If it's correct :PTripos
I meant X is not necessarily an instantiation of a template, but I think you know what I meantPerturbation
@largest_prime_is_463035818, and in this case the function should always return false, as that X cannot be an instantiation of A/B/C/D, if it is not an instatiation of anything, right?Tripos
yep. I am not sure, but I think you only need to add one overload that returns falsePerturbation

© 2022 - 2024 — McMap. All rights reserved.