Короткий способ обернуть статическую переменную-член класса - PullRequest
0 голосов
/ 27 апреля 2018

Представьте, что у вас есть несколько классов, и все они содержат статические переменные одного и того же значения, но их имена различаются в разных классах.

Пример игрушки:

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

Я хочу обернуть переменную «size» дочерним элементом std::integral_constant. Обратите внимание, что я не могу редактировать классы 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> { };

Этот код получает ошибку компиляции:

Ошибка C2953: «Размер »: шаблон класса уже определен

Ответы [ 3 ]

0 голосов
/ 27 апреля 2018

А теперь ... для чего-то совершенно другого ...

Вы можете определить пару getDim() шаблонов constexpr, SFINAE включен / отключен, функций.

Один для типов с 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 шаблонный класс становится напрямую

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, "!" );
 }
0 голосов
/ 27 апреля 2018

Кажется, что, хотя MSVC становится все лучше и лучше, есть еще несколько случаев выражения SFINAE, с которыми он не может справиться. Так что мы просто должны немного помочь. Вместо того, чтобы пытаться специализировать один и тот же шаблон класса или предоставлять две разные функции с одинаковой сигнатурой, мы можем просто предоставить две разные функции и перегрузить их:

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

Это скомпилировано для меня с последним MSVC на Godbolt (а также с gcc и clang).

0 голосов
/ 27 апреля 2018

Хотя это не очень хороший подход, но он подойдет вам:

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