【问题标题】:Implement a type trait for a class template that is true for the actual class template and classes that inherit it为类模板实现一个类型特征,该类型特征对于实际的类模板和继承它的类是正确的
【发布时间】:2022-01-03 23:43:37
【问题描述】:

我有一个像这样的元组类模板

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

现在我需要实现这样的东西

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

IsFoo 可以像这样直接实现

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

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

现在,我还需要 IsFoo 为真,以防传递的类型是公开派生自 Foo 的任何实例化,例如

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

也应该在上面的第一个if constexpr 分支中被视为Foo

但是,我无法弄清楚如何正确实现 IsFoo 特征的模板特化,当 Derived 传递给它时该特征将起作用。但我确信 Stackoverflow 知道怎么做!

编辑:我发现虽然项目使用的所有编译器都支持概念,但没有完整的 C++ 20 支持。所以我决定启用它,以便使用提出的基于概念的解决方案。

【问题讨论】:

  • 我只能想到一个类型特征,它可以告诉你一个类是否是派生的(不一定是公开派生的)。见:en.cppreference.com/w/cpp/types/is_base_of
  • 一种方法是在 Foo 中放置一个标记。另一个是让 Foo 公开继承标记类型。

标签: c++ c++17 typetraits c++-concepts


【解决方案1】:

C++20 概念让事情变得更简单:

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

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

Demo.

【讨论】:

  • 哇,这令人印象深刻。我现在要深入阅读概念;)
【解决方案2】:

编辑: 您可以使用 C++20 概念(老实说,首选)来实现它,但正如您在标签中提到的 C++17,您需要使用丑陋的方式,即void_tDemo 基本上所有的 Foo 实例都应该共享一些共同的“标签”、“标记”等,你可以命名它。 受保护的派生以防止不需要的类型转换,using 将标签提升到公开可见性,然后在 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;
}

下面的旧答案(不完全匹配 OP 的用例)以防万一有人需要它:

您可以利用这样一个事实,即如果某些东西是从一个类公开派生的,那么它也可以转换为它,即转换为它的基类型。

现在,根据您在检测 Foo 方面的确切需要,代码可能会有所不同,但通常应该看起来像这样:

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

【讨论】:

  • 感谢您的回答。但是,它与我的用例完全不匹配,因为它总是指定预期的模板类型。我想要一个在不知道类型的情况下工作的解决方案,其签名例如static_assert (any_foo_or_publicly_derived&lt;BarPub&gt;::value)
  • @PluginPenguin 请立即查看我的答案。
  • 感谢您编辑的答案。我没有想到在这里使用标签,但这是有道理的。不幸的是,Foo 类来自第三方框架,所以我无法更改它(没有提及)。我发现用于该项目的所有编译器都至少有部分 C++ 20 支持,实际上我现在将采用所有支持概念,但否则这将是公认的答案:)
  • @PluginPenguin 如果你会使用概念,那就去尝试吧,你很幸运 ;)
【解决方案3】:

你可以这样做:

  #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");;
  }                                                         

【讨论】:

  • 这是一个不错的、简单的方法,并且表明它不需要标签来完成。但是,可以使用可变参数模板进一步改进,并且可以将 decltype 隐藏在可变模板后面:godbolt.org/z/79MqxqTGh
【解决方案4】:

我的 C++14 版本(可以轻松调整到 C++11):

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-12-08
    • 2023-04-04
    • 1970-01-01
    • 2018-09-08
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多