【问题标题】:A short way to wrap class's static member variable包装类的静态成员变量的简单方法
【发布时间】:2018-10-08 08:20:09
【问题描述】:

假设你有几个类,它们都包含一个含义相同的静态变量,但不同类的名称不同。

一个玩具例子:

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 的孩子包装一个“维度”变量。请注意,我无法编辑“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: 'Dimension': 类模板已定义

【问题讨论】:

  • 似乎是a known problem,在 MSVC 中使用表达式 SFINAE 实现。
  • @IgorTandetnik,所以我最后的代码 sn-p 是正确的,但由于 MSVC 内部问题而无法编译?
  • 这听起来真的很像XY problem。你想让我做什么?如果您只想检索值,则通过模板专业化间接获取
  • @PasserBy 哦,天哪,我尽量不要用额外的信息来使我的问题超载。:) 我想实现一个适用于许多不同类型“点”(内部和外部)的类。我需要在编译时获得“维度”值。但是我想释放用户为常见的“点”类型设置尺寸(例如,使用模板参数)(尽管如此,他需要为其他类型设置它)。 'Dimension' 包装器来了。我想平滑我的解决方案。您认为应该在我的问题中添加什么内容?
  • @PasserBy,我再次浏览了您的链接和我当前的解决方案。不,我不认为存在 XY 问题,因为我在这里唯一想知道 Has...Var 是否可以省略,它们看起来是不必要的。让我们认为给定的代码段周围没有其他任何东西。我只想知道我是否错过了更简洁的实现。

标签: c++ templates c++14 template-meta-programming sfinae


【解决方案1】:

似乎在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&lt;auto&gt;,因此您只需将名称重复两次即可。这样,我们就可以为适当的结果设置别名:

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

这为我使用 Godbolt 上的最新 MSVC(以及 gcc 和 clang)编译。

【讨论】:

  • 这是一个技巧!谢谢你,巴里,它也为我编译。唯一的问题是如果算术类型用完了怎么办?:)
  • @Barry,我喜欢你的解决方案。但是,使用 Type2Type(由 Alexandrescu 编写)而不是算术类型似乎更干净,而且您不必担心类型用完。它的定义如下:template&lt;typename T&gt; struct Type2Type { typedef T OriginalType; };。然后,它被用作所有重载的参数:get_dimensions(Type2Type&lt;T&gt;)。最后,Dimension 将被定义为:template&lt;typename T&gt; using Dimension = decltype(get_dimensions&lt;T&gt;(Type2Type&lt;T&gt;()));
  • @LuisGuzman 我不确定这有什么帮助。而且我不必担心类型用完,请参阅该博客。
【解决方案2】:

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

你可以定义几个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, "!" );
 }

【讨论】:

  • 这是一个很好的解决方案,谢谢你,Max!但它也会在 MSVC 中产生编译错误:'Error C2995 'unknown-type getDim(void)': function template has been defined'
  • @SheiladePope - 您从我的示例或其他代码中得到错误?您的类型可能同时具有dimdimension?在这种情况下,您应该遵循 Barry 的方式(一个接收类型的函数;另一个接收另一种类型的函数)以避免“冲突”。
  • @SheiladePope - 好的:我投降了。我可以和 C++ 争论,但对抗 MSVC 对我来说太难了。
【解决方案3】:

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

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

【讨论】:

  • 哦,是的,这会起作用,但我不能更改“Point”类(因为其中一些是外部库的一部分)。不过,感谢您的回答,我会更新我的问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-09-27
  • 2020-09-19
  • 2010-12-17
  • 1970-01-01
相关资源
最近更新 更多