模板类,方法取决于模板参数

时间:2017-04-04 15:24:05

标签: c++ c++11 templates

我正在编写一个专门用于拍摄随机3D矢量的类,但我在我的项目中使用了几个几何库(一个包含在3D模拟中,一个包含在分析框架中,一个不包含在以上 - 1 GB框架...)。这些库中的每一个都有自己的向量定义,同一方法的名称不同,例如getX(),GetX(),Get(0)...以获得第一个笛卡尔坐标。但有时会采用一种通用的命名约定,并且两个或多个库中的某些方法名称相同 当然我想将这个代码用于任何这些向量,所以我实现了一个模板类。问题如下:如何使我的代码适应所有这些方法名称,而不是为每个实现专门设置我的类(有些共享相同的方法名称)? 我设法用一种方法或另一种方法编写一个类,现在我想推广到任意数量的方法。有些东西说:"如果你有方法1,使用这个实现,如果你有方法2,使用另一个,...如果你没有,那么编译错误"。

目前班级看起来像(缩小到随机方向射击的部分):

// First some templates to test the presence of some methods
namespace detail_rand {
  // test if a class contains the "setRThetaPhi" method
  template<class T>
  static auto test_setRThetaPhi(int) -> 
    decltype(void(std::declval<T>().setRThetaPhi(0.,0.,0.)),
             std::true_type{});

  template<class T>
  static auto test_setRThetaPhi(float)->std::false_type;
}

// true_type if the class contains the "setRThetaPhi" method
template<class T>
struct has_setRThetaPhi : decltype(detail_rand::test_setRThetaPhi<T>(0)) {};


// The actual class
template<class vector>
class Random
{
// everything is static for easy use, might change later
private:
  Random() = delete;
  Random(Random&) = delete;

  // the distribution, random generator and its seed
  static decltype(std::chrono::high_resolution_clock::now().time_since_epoch().count()) theSeed;
  static std::default_random_engine theGenerator;
  static std::uniform_real_distribution<double> uniform_real_distro;

// Shoot a direction, the actual implementation is at the end of the file
private: // the different implementations
  static const vector Dir_impl(std::true_type const &);
  static const vector Dir_impl(std::false_type const &);

public: // the wrapper around the implementations
  inline static const vector Direction() {
    return Dir_impl(has_setRThetaPhi<vector>());
  }
};


/// initialisation of members (static but template so in header)
// the seed is not of cryptographic quality but here it's not relevant
template<class vector> 
decltype(std::chrono::high_resolution_clock::now().time_since_epoch().count())
Random<vector>::theSeed = 
  std::chrono::high_resolution_clock::now().time_since_epoch().count();

template<class vector>
std::default_random_engine Random<vector>::theGenerator(theSeed);

template<class vector>
std::uniform_real_distribution<double> Random<vector>::uniform_real_distro(0.,1.);


/// Implementation of method depending on the actual type of vector
// Here I use the "setRThetaPhi" method
template<class vector>
const vector Random<vector>::Dir_impl(std::true_type const &)
{
  vector v;
  v.setRThetaPhi(1.,
                 std::acos(1.-2.*uniform_real_distro(theGenerator)),
                 TwoPi()*uniform_real_distro(theGenerator));
  return std::move(v);
}

// Here I use as a default the "SetMagThetaPhi" method
// but I would like to test before if I really have this method,
// and define a default implementation ending in a compilation error
// (through static_assert probably)
template<class vector>
const vector Random<vector>::Dir_impl(std::false_type const &)
{
  vector v;
  v.SetMagThetaPhi(1.,
                   std::acos(1.-2.*uniform_real_distro(theGenerator)),
                   TwoPi()*uniform_real_distro(theGenerator));
  return std::move(v);
}

2 个答案:

答案 0 :(得分:1)

  

有些东西说:&#34;如果你有方法1,使用这个实现,如果你有方法2,使用另一个,...如果你没有,那么编译错误&#34;。

我写了一篇文章,解释了如何在C ++ 11,C ++ 14和C ++ 17中正确实现所需的内容:"checking expression validity in-place with C++17"

我将合成下面的C ++ 11和C ++ 14解决方案 - 您可以使用它们来规范化您所处理的所有接口,方法是将它们包装在单个&#34; common&#34;一。然后,您可以在&#34; common&#34;上实现您的算法。接口

假设你有:

struct Cat { void meow() const; };
struct Dog { void bark() const; };

你想创建一个函数模板 make_noise(const T& x),如果有效则调用x.meow(),否则x.bark()如果有效,否则会产生编译错误。

在C ++ 11中,您可以使用enable_ifdetection idiom

您需要为要检查其存在的每个成员创建类型特征。例如:

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

template <typename T>
struct has_meow<T, void_t<decltype(std::declval<T>().meow())>>
    : std::true_type { };

以下是使用enable_if尾随返回类型的使用示例 - 此技术使用expression SFINAE

template <typename T>
auto make_noise(const T& x)
    -> typename std::enable_if<has_meow<T>{}>::type
{
    x.meow();
}

template <typename T>
auto make_noise(const T& x)
    -> typename std::enable_if<has_bark<T>{}>::type
{
    x.bark();
}

在C ++ 14中,你可以使用通用lambdas static_if 的实现(这里&#39; sa talk I gave at CppCon 2016关于可能的一个)< / em>使用类似命令的语法执行检查。

您需要一些实用程序:

// Type trait that checks if a particular function object can be 
// called with a particular set of arguments.
template <typename, typename = void>
struct is_callable : std::false_type { };

template <typename TF, class... Ts>
struct is_callable<TF(Ts...),
    void_t<decltype(std::declval<TF>()(std::declval<Ts>()...))>>
    : std::true_type { };

// Wrapper around `is_callable`.
template <typename TF>
struct validity_checker
{
    template <typename... Ts>
    constexpr auto operator()(Ts&&...) const
    {
        return is_callable<TF(Ts...)>{};
    }
};

// Creates `validity_checker` by deducing `TF`.
template <typename TF>
constexpr auto is_valid(TF)
{
    return validity_checker<TF>{};
}

之后,您可以在make_noise

的单个重载内执行所有检查
template <typename T>
auto make_noise(const T& x)
{
    auto has_meow = is_valid([](auto&& x) -> decltype(x.meow()){ });
    auto has_bark = is_valid([](auto&& x) -> decltype(x.bark()){ });

    static_if(has_meow(x))
        .then([&x](auto)
            {
                x.meow();
            })
        .else_if(has_bark(x))
        .then([&x](auto)
            {
                x.bark();
            })
        .else_([](auto)
            {
                // Produce a compiler-error.
                struct cannot_meow_or_bark;
                cannot_meow_or_bark{};
            })(dummy{});
}

一些宏黑魔法if constexpr允许你用C ++ 17写这个:

template <typename T>
auto make_noise(const T& x)
{
    if constexpr(IS_VALID(T)(_0.meow()))
    {
        x.meow();
    }
    else if constexpr(IS_VALID(T)(_0.bark()))
    {
        x.bark();
    }
    else
    {
        struct cannot_meow_or_bark;
        cannot_meow_or_bark{};
    }
}

答案 1 :(得分:0)

您可以通过为操作引入自己的名称来解决此问题。通过创建特征类并为每个库专门化它来完成此操作。像这样:

template <class Vector>
struct VectorTraits;

template <>
struct VectorTraits<Lib1::Vector>
{
  static auto getX(const Lib1::Vector &v) { return v.GetX(); }
  // ... etc.
};

template <>
struct VectorTraits<Lib2::Vector>
{
  static auto getX(const Lib2::Vector &v) { return v.Get(0); }
  // ... etc.
};

//Usage:

template <class vector>
auto norm2(const vector &v)
{
  using V = VectorTraits<vector>;
  return V::getX(v) * V::getX(v) + V::getY(v) + V::getY(v);
}

如果您想要不受支持的操作的静态断言,可以将它们放入非专用模板中:

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

template <class Vector>
struct VectorTraits
{
  static void getX(const Vector &)
  {
    static_assert(False<Vector>::value, "This type does not support getting x");
  }
};