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;
}