A short way to wrap class's static member variable
Asked Answered
D

3

10

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

Dicta answered 27/4, 2018 at 13:13 Comment(7)
Seems to be a known problem with expression SFINAE implementation in MSVC.Monger
@IgorTandetnik, so my last code snippet is correct, but doesn't compile because of MSVC internal issues?Dicta
Still, is there a nicer solution that will work with MSVC 2017?Dicta
This really sounds like a XY problem. What do you want to do? If you just want to retrieve the value, indirect through a template specializationKeeleykeelhaul
@PasserBy Oh my, I tried not to overload my question with extra info.:) I want to implement a class that works with many different kinds of 'Points' (both internal and external). I need to get 'dimension' value at compile time. But I want to free the user of setting dimension (for example, with a template parameter) for common 'Point' types (nevertheless, he will need to set it for other types). Here comes 'Dimension' wrapper. And I want to smoothen my solution for it. What do you think should be added to my question?Dicta
I have no idea. I only have the feeling you are trying to solve another problem in a convoluted way.Keeleykeelhaul
@PasserBy, I scanned through your link and my current solution once more. No, I don't think there is a XY-problem, cause the only thing I want to know here whether Has...Var can be omited or not, they look like something unnecessary. Let's think there's nothing else around the given piece of code. I just want to know whether I miss a cleaner implementation.Dicta
K
2

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

Kauri answered 27/4, 2018 at 19:3 Comment(3)
That's a trick! Thank you, Barry, it compiles for me too. The only question is what if we run out of arithmetic types?:)Dicta
@Barry, I like your solution. However, using Type2Type (by Alexandrescu) instead of arithmetic types seems cleaner, and you don't need to worry about running out of types. It is defined like this: template<typename T> struct Type2Type { typedef T OriginalType; };. Then, it is used as the parameter for all overloads: get_dimensions(Type2Type<T>). Finally, Dimension would be defined like this: template<typename T> using Dimension = decltype(get_dimensions<T>(Type2Type<T>()));Cootch
@LuisGuzman I'm not sure how that helps. And I don't have to worry about running out of types, see that blog.Kauri
C
1

And now... for something completely different...

You could define a couple of getDim() template constexpr, SFINAE enabled/disabled, functions.

One for types with dim

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

and one for types with dimension

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

You can add other template functions if needed.

Now your Dimension template class become directly

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

or also, if you want do give a different name to the variable

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

The following is a full compiling example

#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, "!" );
 }
Compo answered 27/4, 2018 at 16:48 Comment(3)
It's a nice solution, thank you, Max! But it also produces a compile error in MSVC: 'Error C2995 'unknown-type getDim(void)': function template has already been defined'Dicta
@SheiladePope - You get the error from my example or from a different code? It's possible that you have a type with both dim and dimension? In this case, you should follow the Barry's way (a function that receive a type; another function that receive another type) to avoid "collisions".Compo
@SheiladePope - OK: I surrender. I can argue with C++ but fighting against MSVC is too difficult for me.Compo
R
-1

Although this is not a good approach, but this will work for you:

#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;
};
Roselane answered 27/4, 2018 at 13:33 Comment(1)
Oh yes, this will work, but I can't change 'Point' classes (because some of them are part of external libraries). Nevertheless, thanks for your answer, I will update my question.Dicta

© 2022 - 2024 — McMap. All rights reserved.