Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

A short way to wrap class's static member variable

Imagine that you have several classes and all of them contain a static variable of the same meaning, but it's names differ in different classes.

A toy example:

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

I want to wrap a "dimension" variable with a std::integral_constant's child. Note that I can't edit 'Point' classes, because they are parts of some external libraries. This implementation works for me, but looks clumsy (I'm using 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> { };

Is there a way to skip all these HasSomeVars and have something short and clear like this:

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

This code gets a compile error:

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

like image 424
Sheila de Pope Avatar asked Apr 27 '18 13:04

Sheila de Pope


People also ask

Which is the best way to declare a static variable?

Static variables can be accessed by calling with the class name ClassName. VariableName. When declaring class variables as public static final, then variable names (constants) are all in upper case. If the static variables are not public and final, the naming syntax is the same as instance and local variables.

How do you declare a static member variable in C++?

We can define class members static using static keyword. When we declare a member of a class as static it means no matter how many objects of the class are created, there is only one copy of the static member. A static member is shared by all objects of the class.

How do you use a static member variable?

Static Member Functions in C++ To create a static member function we need to use the static keyword while declaring the function. Since static member variables are class properties and not object properties, to access them we need to use the class name instead of the object name.

How do you initialize a static member of a class in C++?

We can put static members (Functions or Variables) in C++ classes. For the static variables, we have to initialize them after defining the class. To initialize we have to use the class name then scope resolution operator (::), then the variable name. Now we can assign some value.


1 Answers

It seems that while MSVC is getting better and better, there are still a few cases of expression SFINAE that it can't quite deal with with. So we just gotta help it out a little bit. Instead of trying to specialize the same class template or provide two different functions with the same signature, we can just provide two different functions and overload on them:

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

It doesn't seem like MSVC supports template<auto> yet, so you'll just have to repeat the name twice. With that, we can just alias the appropriate result:

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

This compiles for me with the latest MSVC on godbolt (as well as gcc and clang).

like image 52
Barry Avatar answered Nov 07 '22 08:11

Barry