包装class的静态成员变量的一种简短方法

A short way to wrap class's static member variable

假设您有多个 类,并且它们都包含一个具有相同含义的静态变量,但它的名称在不同 类 中有所不同。

玩具示例:

class Point2D
{
public:
    static constexpr int dimension = 2;
private:
    double x, y;
} 

class Point3D
{
public:
    static constexpr int dim = 3;
private:
    double x, y, z;
};

我想用 std::integral_constant 的 child 包装一个 "dimension" 变量。请注意,我无法编辑 'Point' 类,因为它们是某些外部库的一部分。这个实现对我有用,但看起来很笨拙(我使用的是 VS2017):

template <typename T, typename = void>
struct HasDimensionVar : std::false_type { };
template <typename T>
struct HasDimensionVar<T, decltype( T::dimension, void( ) )> : std::true_type { };

template <typename T, typename = void>
struct HasDimVar : std::false_type { };
template <typename T>
struct HasDimVar<T, decltype( T::dim, void( ) )> : std::true_type { };

template <typename T, class Enable = void>
struct Dimension;

template <typename T>
struct Dimension<T, std::enable_if_t< HasDimensionVar<T>::value> > :
    std::integral_constant<decltype( T::dimension ), T::dimension> { };

template <typename T>
struct Dimension<T, std::enable_if_t< HasDimVar<T>::value> > :
    std::integral_constant<decltype( T::dim ), T::dim> { };

有没有办法跳过所有这些 HasSomeVars 并像这样简短明了:

template <typename T, class Enable = void>
struct Dimension;

template <typename T>
struct Dimension<T, decltype( T::dimension, void( ) ) > :
    std::integral_constant<decltype( T::dimension ), T::dimension> { };

template <typename T>
struct Dimension<T, decltype( T::dim, void( ) ) > :
    std::integral_constant<decltype( T::dim ), T::dim> { };

此代码出现编译错误:

Error C2953: 'Dimension< T, unknown-type >': class template has already been defined

虽然这不是一个好方法,但是这对你有用:

#define DEFVAR(nm,val) static constexpr int nm = val;

class Point2D
{
public:
    DEFVAR(dimension,2)
private:
    double x, y;
};

class Point3D
{
public:
    DEFVAR(dim,3)
private:
    double x, y, z;
};

现在...完全不同的东西...

您可以定义几个 getDim() 模板 constexpr、SFINAE enabled/disabled、函数。

具有 dim

的类型
template <typename T>
constexpr auto getDim () -> decltype( T::dim )
 { return T::dim; }

还有一个用于具有 dimension

的类型
template <typename T>
constexpr auto getDim () -> decltype( T::dimension )
 { return T::dimension; }

如果需要,您可以添加其他模板功能。

现在你的Dimension模板class直接变成了

template <typename T>
struct Dimension
   : std::integral_constant<decltype(getDim<T>()), getDim<T>()>
 { };

或者,如果你想给变量起一个不同的名字

template <typename T>
struct Dimension
 {
   static constexpr auto dim { getDim<T>() };
 };

下面是一个完整的编译示例

#include <iostream>

class Point2D
 {
   public:
      static constexpr int dimension = 2;

   private:
      double x, y;
 };

class Point3D
 {
   public:
      static constexpr int dim = 3;

   private:
      double x, y, z;
 };


template <typename T>
constexpr auto getDim () -> decltype( T::dim )
 { return T::dim; }

template <typename T>
constexpr auto getDim () -> decltype( T::dimension )
 { return T::dimension; }

template <typename T>
struct Dimension
   : std::integral_constant<decltype(getDim<T>()), getDim<T>()>
 { };

int main()
 {
   Dimension<Point2D>  d2; // compile
   Dimension<Point3D>  d3; // compile

   //Dimension<int>  di;  // compilation error

   static_assert( Dimension<Point2D>::value == 2, "!" );
   static_assert( Dimension<Point3D>::value == 3, "!" );
 }

看来,虽然MSVC越来越好,但还是有一些表达SFINAE的情况它不能很好地处理。所以我们只需要帮助它一点点。与其尝试专门化相同的 class 模板或提供两个具有相同签名的不同函数,我们可以只提供两个 不同的 函数并重载它们:

namespace detail {
    template <typename T>
    constexpr std::integral_constant<decltype(T::dim), T::dim>
    get_dimensions(int)
    {
        return {};
    }

    template <typename T>
    constexpr std::integral_constant<decltype(T::dimension), T::dimension>
    get_dimensions(long)
    {
        return {};
    }
}

MSVC 似乎还不支持 template<auto>,因此您只需重复该名称两次即可。这样,我们就可以为适当的结果添加别名:

template <typename T>
using Dimension = decltype(detail::get_dimensions<T>(0));

这为我编译了 godbolt 上的最新 MSVC(以及 gcc 和 clang)。