【问题标题】:Restrict supported types of a template class by using variadic templates使用可变参数模板限制模板类的支持类型
【发布时间】:2014-08-19 17:42:30
【问题描述】:

我正在尝试处理仅与以下某些组合兼容的图像处理操作类:

  • 一组维度 [1,2,3, ...]
  • 一组类型 [int, float, double, ...]

一种工作方法是定义一个通用模板类来处理默认情况(即:什么都不做),如下所示:

template <int dimension, typename dataType>
class IPOperation
{
public:
    void execute()
    {
        std::cout << "do nothing" << std::endl;
    }
};

然后我必须为所有支持的类型组合编写该类的特化,例如:

template<>
class IPOperation<2,float>
{
public:
    void execute()
    {
        std::cout << "do something" << std::endl;
    }
};

但是由于支持的维度/类型组合的数量可能会变得非常大,所以这种方法完全是矫枉过正..

受策略驱动设计的启发,最好为模板类配备特定限制,如下所示:

template <int dimension, typename dataType>
class IPOperation : public supportedDimensions<2,3,4>, public supportedTypes<int, float, double>
{
    void execute()
    {
        std::cout << "execute()" << std::endl;
    }
};

到目前为止,我的糟糕做法是这样的(看起来很糟糕,我情不自禁):

#include <iostream>
#include <vector>

template <int... supportedDimensions>
class IPDimensions
{
private:
public:
    static std::vector<int> getSupportedDimensions()
    {
        return { supportedDimensions... };
    }

    static bool supportsDimension(int dim)
    {
        std::vector<int> dimensions = getSupportedDimensions();
        return std::find(dimensions.begin(), dimensions.end(), dim) != dimensions.end();
    }
};

template <int dim>
class IPOperation : public IPDimensions<2,3>
{
private:
public:
    void execute(void)
    {
        if(IPOperation::supportsDimension(dim))
        {
            std::cout << dim << "d is supported -> execute" << std::endl;
        }
        else
        {
            std::cout << dim << "d is not supported -> sit down and do nothing" << std::endl;
        }
    }
};

int main(int argc, const char * argv[])
{
    IPOperation<2>* okay = new IPOperation<2>();
    IPOperation<4>* notOkay = new IPOperation<4>();

    okay->execute();
    notOkay->execute();
}

当试图对类型应用这样的东西时,我完全迷失了。最好有某种机制来做一些检查,就像你使用策略作为特定策略的代表一样。也许我的方法是错误的,这整个事情可以通过宏、枚举或特征和 std::enable_if 来更简单地实现,以使函数只对定义的场景可见,但是因为我花了一些时间阅读一些 c++11 主题,我真的什么都不确定了。

提前感谢你们提供任何有用的建议!

【问题讨论】:

  • 这些类中是否存储了任何状态?如果是无状态的,它们应该是函数吗?如果函数,标签调度。如果不这样做,可能会在方法上进行标记分离。

标签: c++ templates c++11 variadic-templates


【解决方案1】:

如果您想对不受支持的类型进行虚拟操作(但不会出现编译错误),您可以使用以下内容:

#include <type_traits>
#include <iostream>

template <class...>
struct supportedTypes {
    template <class X>
    static constexpr bool check() { return false; };
};
template <class A, class... R>
struct supportedTypes<A, R...> {
    template <class X>
    static constexpr bool check() {
        return std::is_same<X, A>::value
        || supportedTypes<R...>::template check<X>(); }
};

int main() {
    std::cout << supportedTypes<int,double>::check<int>();
    std::cout << supportedTypes<int,double>::check<void>();
}

【讨论】:

  • 我发现这个解决方案最适合我的目的。这种方法允许我在保持代码简洁的同时表达限制:无需专门化类或函数,并且语法易于阅读和根据需要修改。感谢@firda 的回答!我肯定要花很多时间来思考 c++11 和 c++14 中的模板元编程能够做什么!
  • 如果检查失败,您可以提供虚拟/任何操作,或者将检查放在static_assert 中并很好地解释什么是错误的(比编译器错误消息要好得多)。 C++11 真的很好,我自己还在学习所有好的变化。 C++14 引入了一些不错的更正和助手,例如 enable_if_t 快捷方式或 remove_reference_t ...我最近需要 template&lt;T&gt; using remove_cref_t = remove_const_t&lt;remove_reference_t&lt;T&gt;&gt; 来处理我的 pack 助手(如 tuple 但打包,从左到右和也可以使用 c 数组)。祝你好运!
【解决方案2】:

我使用 SFINAE 来解决这类问题,例如:

template<std::size_t dimensions, typename dataType, typename=void> class IPOperation;

template<std::size_t dimensions, typename dataType>
class IPOperation<dimensions, dataType,
                  std::enable_if<(0<dimensions && dimensions<4) && 
                                 std::is_floatingpoint<dataType>::value >::type>
{
   /* ... */
};

IPOperation<2,float> a; // okay
IPOperation<4,float> b; // compile-time error: wrong dimensionality
IPOperation<3,int>   c; // compile-time error: wrong dataType

static_assert:

template<std::size_t dimensions, typename dataType>
class IPOperation<dimensions, dataType>
{
   static_assert(0<dimensions && dimensions<4,"wrong dimension");
   static_assert(std::is_floatingpoint<dataType>::value,"incompatible data type");
   /* ... */
};

这会提供更好的错误消息。

【讨论】:

    【解决方案3】:

    您可以利用boost::mpl::set 检查是否支持给定的维度/类型。如果您想在编译时拒绝不支持的类型,请使用static_assert;如果您确实想要“默认情况下不执行任何操作”逻辑,请使用 SFINAE。

    static_assert

    #include <iostream>
    
    #include <boost/mpl/set.hpp>
    #include <boost/mpl/set_c.hpp>
    
    using namespace boost::mpl;
    
    template <std::size_t Dim, typename Type>
    struct Op {
    
      static_assert(has_key<set_c<std::size_t, 2, 3, 4>,
                            integral_c<std::size_t, Dim>>::value,
                    "Unsupported dimension!");
      static_assert(has_key<set<int, float, double>, Type>::value,
                    "Unsupported type!");
    
      void Execute() {
        std::cout << "DoSomething" << std::endl;
      }
    
    };
    
    int main() {
      // Op<1, int> x;  // error: Unsupported dimension!
      // Op<2, std::string> x;  // error: Unsupported type!
      Op<2, int> x;
      x.Execute();
    }
    

    打印:

    DoSomething
    

    SFINAE

    #include <iostream>
    
    #include <boost/mpl/set.hpp>
    #include <boost/mpl/set_c.hpp>
    
    using namespace boost::mpl;
    
    template <std::size_t Dim, typename Type, typename = void>
    struct Op {
    
      void Execute() {
        std::cout << "DoNothing" << std::endl;
      }
    
    };
    
    template <std::size_t Dim, typename Type>
    struct Op<Dim,
              Type,
              std::enable_if_t<has_key<set_c<std::size_t, 2, 3, 4>,
                                       integral_c<std::size_t, Dim>>::value &&
                               has_key<set<int, float, double>, Type>::value>> {
    
      void Execute() {
        std::cout << "DoSomething" << std::endl;
      }
    
    };
    
    int main() {
      Op<1, int> x;
      Op<2, int> y;
      x.Execute();
      y.Execute();
    }
    

    打印:

    DoNothing
    DoSomething
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-01-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-10-01
      • 1970-01-01
      • 1970-01-01
      • 2021-03-08
      相关资源
      最近更新 更多