Implement a type trait for a class template that is true for the actual class template and classes that inherit it

Viewed 360

I have a tuple-like class template like this

template <class... T>
struct Foo {}

Now I need to implement something like this

template <class T>
void bar (const T& t)
{
    if constexpr (IsFoo<T>::value)
        // treat it as Foo
    else
        // a generic solution
}

IsFoo can be implemented straightforward like this

template <class T>
struct IsFoo : std::false_type {}

template <class... T>
struct IsFoo<Foo<T...>> : std::true_type {}

Now, I also need IsFoo to be true in case the type passed is publicly derived from any instantiation of Foo, e.g.

struct Derived : public Foo<int, float> {}

should also be treated like a Foo in the first if constexpr branch above.

However, I can't figure out how to properly implement a template specialisation of my IsFoo trait that would work when Derived is passed to it. But I'm sure Stackoverflow knows how to!

Edit: I found out that although all compilers used for the project support concepts, although there is no full C++ 20 support. So I decided to enable that in order to use the concepts-based solutions proposed.

4 Answers

C++20 concepts make things much easier:

template <class... Ts>
struct Foo {};

template<class T>
concept IsFoo = requires(T& t){ 
  []<class... Ts>(Foo<Ts...>&){}(t);  
};

Demo.

Edited: You can achieve it either with C++20 concepts (preferred, to be honest), but as you've mentioned C++17 in tags, you'll need to use the ugly way, i.e. void_t. Demo Basically all instances of Foo should share some common "tag", "marker" etc., you name it. Protected derivation to prevent unwanted typecasts, using raises the tag up to public visibility and then the check for its public existence happnes inside void_t.

#include <type_traits>
#include <iostream>

struct FooBase
{
    struct FooTag{};
};

template<typename ...Args>
struct Foo : protected FooBase
{
    using FooBase::FooTag;
};

using FooIF = Foo<int, float>;
using FooIU = Foo<int, unsigned>;
using FooC = Foo<char>;

struct BarPub : public FooIF {};
struct BarPrv : private FooIU {};
struct BarPrt : protected FooC {};
struct Baz {};

template<typename T, typename=void>
struct is_publicly_derived_from_any_foo : std::false_type
{};

template<typename T>
struct is_publicly_derived_from_any_foo<T,
    std::void_t<decltype(typename T::FooTag{})>> 
    : std::true_type
{};

int main(int, char*[])
{
    std::cout << is_publicly_derived_from_any_foo<FooIF>::value << "\n";
    std::cout << is_publicly_derived_from_any_foo<FooC>::value << "\n";
    std::cout << is_publicly_derived_from_any_foo<FooIU>::value << "\n";

    std::cout << is_publicly_derived_from_any_foo<Baz>::value << "\n";

    std::cout << is_publicly_derived_from_any_foo<BarPub>::value << "\n";
    std::cout << is_publicly_derived_from_any_foo<BarPrv>::value << "\n";
    std::cout << is_publicly_derived_from_any_foo<BarPrt>::value << "\n";

    return 0;
}

Old answer below (not fully matching OP's use case) in case anyone needed it:

You can take advantage of the fact, that if something is publicly derived from a class, it is also convertible to it, i.e. to its base type.

Now, depending what you exactly need in terms of detecting Foo the code might differ, but generally it should look +- like this:

Demo

#include <iostream>
#include <type_traits>
           
template <class... T>
struct Foo {};

template <class T>
struct IsFoo : std::false_type {};

template <class... T>
struct IsFoo<Foo<T...>> : std::true_type {};


class BarPub : public Foo<int, float> {};
class BarPriv : private Foo<int, float> {};
class BarProt : protected Foo<int, float> {};
using FooIF = Foo<int, float>;


template<typename T, typename U>
struct is_public_base_of 
: std::conjunction<
        std::is_convertible<T, U>,
        std::is_base_of<U, T>>
{};
//extra disjunction with std::is_same<U,T>
//for fundamental types if it's also needed

template<typename T, typename ...Args>
struct typed_foo_or_publicly_derived:
    is_public_base_of<T, Foo<Args...>>
{}; 

template<typename T, typename ...Args>
struct any_foo_or_publicly_derived:
    std::disjunction<
        IsFoo<T>,
        is_public_base_of<T, Foo<Args...>>>
{}; 

int main()
{   
    std::cout << is_public_base_of<BarPub, Foo<int, float>>::value << "\n";
    std::cout << is_public_base_of<BarPriv, Foo<int, float>>::value << "\n";
    std::cout << is_public_base_of<BarProt, Foo<int, float>>::value << "\n";
    std::cout << is_public_base_of<FooIF, Foo<int, float>>::value << "\n";
    std::cout << typed_foo_or_publicly_derived<BarPub, int, float>::value << "\n";
    std::cout << typed_foo_or_publicly_derived<BarPriv, int, float>::value << "\n";
    std::cout << typed_foo_or_publicly_derived<BarProt, int, float>::value << "\n";
    std::cout << typed_foo_or_publicly_derived<FooIF, int, float>::value << "\n";
    return 0;
}

You can do it this way:

  #include <type_traits>
           
  template <class A, class B> class Foo {};
           
  class Bar : public Foo<int, float> {};
           
  template <class A, class B> 
  std::true_type checkFoo(const Foo<A,B>&);
      
  std::false_type checkFoo(...);
      
  int main()
  {   
      Bar bar;
      int baz;
      static_assert(decltype(checkFoo(bar))::value, "bar test failed");;
      static_assert(!decltype(checkFoo(baz))::value, "baz test failed");;
  }                                                         

My version with C++14 (can be tweaked to C++11 easily):

template <template<typename...> typename T, class... U>
void callWithTemplateBase(const T<U...>&);

template <typename T, template<typename...> typename Base, typename U = void>
struct IsInstanceOfTemplate : std::false_type {};

template <typename T, template<typename...> typename Base>
struct IsInstanceOfTemplate<
        T, 
        Base, 
        decltype(callWithTemplateBase<Base>(std::declval<T>()))> 
    : std::true_type {};

template <typename T, template<typename...> typename Base>
constexpr bool IsInstanceOfTemplate_v = IsInstanceOfTemplate<T, Base>::value;


template <class... T>
struct Foo {};

template<typename T>
constexpr bool IsFoo_v = IsInstanceOfTemplate_v<T, Foo>;

https://godbolt.org/z/sv7jTPbE6

Related