【问题标题】:Apply SFINAE to check if a trait is defined for T应用 SFINAE 检查是否为 T 定义了特征
【发布时间】:2015-10-15 08:25:20
【问题描述】:

我目前正在研究一个小型数学向量类。 我想要两个向量类,Vector2Vector3 可以从一个到另一个构造。 例如:

Vector2<float> vec2(18.5f, 32.1f); // x = 18.5; y = 32.1
Vector3<float> vec3(vec2);         // x = 18.5; y = 32.1; z = float()

为此,为了简化可扩展性,我想使用带有基本定义的特征VectorTraits

template <typename T>
struct VectorTraits
{
  typedef T                      VectorType;
  typedef typename T::ValueType  ValueType;

  static const unsigned int      dimension = T::dimension;
};

此表单将允许用户在现有的 Vectors 类(例如 glm::vec2)和我的类之间建立链接。然后可以从 glm::vec2 创建 Vector2。

此外,这种技术可以让我为所有使用 SFINAE 定义 VectorTraits 的类编写通用流式操作符。

我的问题是,我无法定义operator&lt;&lt;,所以当VectorTraits 不适合给定类型时,这是一个静默错误。

这是我最后一次尝试 (Ideone link here):

#include <iostream>
#include <type_traits>

// To define another operator
struct Dummy
{};

// Traits class
template <typename T>
struct VectorTraits
{
  typedef T                       VectorType;
  typedef typename T::ValueType   ValueType;
  static const std::uint16_t      dimension = T::dimension;
};

// Fake vector class. Defines the required typedef.
struct Vec
{
  typedef float   ValueType;
  static const std::uint16_t dimension = 2;
};

// Streaming operator for Dummy.
std::ostream& operator<<(std::ostream& stream, const Dummy& d)
{
    stream << "dummy.\n";
    return stream;
}

// Streaming operator attempt for classes defining VectorTraits.
template <class T, std::enable_if_t<(VectorTraits<T>::dimension > 0)>>
std::ostream& operator<<(std::ostream& stream, const T& vec)
{
    std::cout << "Traits. Dimension = " << VectorTraits<T>::dimension << "\n";
}

int main()
{
    std::cout << "Test\n";
    std::cout << Vec();
    std::cout << Dummy();
    return 0;
}

通过这种尝试,错误只是

error: no match for 'operator<<' (operand types are 'std::ostream {aka std::basic_ostream<char>}' and 'Vec')
prog.cpp:33:15: note: candidate: template<class T, typename std::enable_if<(VectorTraits<T>::dimension > 0), void>::type <anonymous> > std::ostream& operator<<(std::ostream&, const T&)
 std::ostream& operator<<(std::ostream& stream, const T& vec)
               ^
prog.cpp:33:15: note:   template argument deduction/substitution failed:
prog.cpp:41:19: note:   couldn't deduce template parameter '<anonymous>'

如果我改变了

template <class T, std::enable_if_t<(VectorTraits<T>::dimension > 0)>>

template <class T, std::enable_if_t<(VectorTraits<T>::dimension > 0)>* = 0>

我得到另一个错误

prog.cpp:13:35: error: 'char [21]' is not a class, struct, or union type
   typedef typename T::ValueType   ValueType;

我设法开始工作的唯一版本是一个空的VectorTraits 类,它必须专门用于每个Vector。但我也想提供一种“自动”成为 Vector 并定义了一些 typedefs 的方法。

我不明白为什么在显示版本中,我的运算符没有被编译器保留。我也尝试了一些变体,但它总是要么匹配所有内容,要么什么都不匹配。

【问题讨论】:

  • 您是否想要一个只选择 your 向量的特征(在这种情况下,您有一个空的主模板并专门针对您的向量),或者您是否需要想要检测 any 类向量类型。在后一种情况下,您需要一个通用的存在检查特征来检查一个类型是否具有某种类型的成员等。
  • 就我而言,我真的希望能够厌恶任何类似Vector 的类型。但我一直无法理解如何正确实现这一目标。不仅仅是解决方案,我真的很想了解我在这里缺少什么。
  • 如果您指定该实现的实际问题(发布实际错误消息或您得到的任何内容),这将有所帮助。正如现在写的那样,它只是“不起作用”。
  • (有点切题,pretty printer code 包含一个检测“任何看起来像容器的东西”的特征。也许这让您对用于此类选择机制的那种 TMP 有所感觉。)跨度>
  • @Petr 我忘了这样做。我已经用更多信息更新了这个问题。

标签: c++ templates c++11 sfinae


【解决方案1】:

一个问题是您没有为std::enable_if_t 实例化的结果提供默认参数,因此模板参数推导失败。解决此问题的一种方法是将* = nullptr 添加到其中:

template <class T, std::enable_if_t<(VectorTraits<T>::dimension > 0)>* = nullptr>
std::ostream& operator<<(std::ostream& stream, const T& vec)

但是,现在我们得到一个错误,因为在 VectorTraits&lt;T&gt; 实例化中,T::ValueType 是必需的。这不在 SFINAE 上下文中,因此如果该成员不存在,则会发生硬故障。我们可以通过在模板参数中添加 SFINAE 检查来解决此问题:

template <class T, typename = typename T::ValueType, 
          std::enable_if_t<(VectorTraits<T>::dimension > 0)>* = nullptr>
std::ostream& operator<<(std::ostream& stream, const T& vec)

您可以将此因素考虑到外部 IsValidVector 检查中,以便在多次需要此类检查时可以单点更新。

【讨论】:

  • 我不清楚的是为什么在 OP 的原始代码中,只有 &lt;&lt;Vec() 给出了错误。为什么在 OP 的 Dummy 的原始代码中不会出现与第二个示例相同的硬故障?
  • 感谢您的回答。我注意到您在第一部分中指出的错误,并将其作为请求进行了编辑。但是,您的第二个解决方案不符合我的期望,因为它需要T 来定义类型ValueType。这将阻止用户手动定义VectorTraits&lt;glm::vec2&gt;
  • @Petr 我手动为Dummy 定义了operator&lt;&lt;,所以它总是比有问题的匹配更好。
  • @Petr:有std::ostream&amp; operator&lt;&lt;(std::ostream&amp; stream, const Dummy&amp; d),而有从const char[N]const char*的转换。
  • @Ninetainedo 啊,我明白了。在这种情况下,您可能最好使用 Jarod42 的解决方案,因为您可以完全专业化 VecTraits_Impl
【解决方案2】:

您可以在 vectorTrait 中添加一个图层,使其仅在有效时启用:

// Traits class
template <typename T, typename = void>
struct VectorTraits_impl
{
};


template <typename T>
struct VectorTraits_impl<T,
                        std::enable_if_t<std::is_integral<decltype(T::dimension)>::value>>
{
  typedef T                       VectorType;
  typedef typename T::ValueType   ValueType;
  static const std::uint16_t      dimension = T::dimension;
};

// Traits class
template <typename T>
using VectorTraits = VectorTraits_impl<T>;

Demo

【讨论】:

  • 感谢您的回答。但是这个不允许用户专门化VectorTraits 来让我的班级“认为”他的特定类型是Vector。 (例如here
  • @Ninetainedo 专门 VectorTraits_impl,例如 this
  • 但是,当我再次考虑它时,我只需要交换VectorTraits_implVectorTraits 以便用户可以在我使用VectorTraits_impl 时专门化VectorTraits。嗯,我想那会做到的。让我试试看,我会回来将其标记为已解决。
  • @TartanLlama 是的,谢谢。我觉得我太专注于VectorTraits
  • 如果您对模板有疑问,请添加更多模板。现在你有std::integral_constant&lt;int,N+1&gt; 问题。
【解决方案3】:

由于以下原因,您的特定代码无法正常工作。

对于您的向量,std::enable_if_t&lt;(VectorTraits&lt;T&gt;::dimension &gt; 0)&gt; 是一些类型。因此,您将operator&lt;&lt; 声明为模板,其中第二个参数是该类型的值。对于Dummy(和char*)没有这样的类型,所以SFINAE排除了这个。但是对于Vec,您的std::enable_if_t&lt;(VectorTraits&lt;T&gt;::dimension &gt; 0)&gt; 是某个type,编译器期望模板参数是该类型的某个value。当然,它没有办法找出那个值应该是什么。

【讨论】:

  • 感谢您的回答。对于Dummychar*,有比我的模板更好的匹配,所以他们甚至从不考虑我的超载。对于 std::enable_if_t&lt;(VectorTraits&lt;T&gt;::dimension &gt; 0)&gt; 是某种类型,当我重写上面的代码时这是一个错误。我编辑在代码 sn-p 下方添加了更多信息。
【解决方案4】:

Jarod42 提供的解决方案不适用于 Visual Studio,因此我设法找到了另一个解决方案。我发布它是为了帮助那些一天或一天​​遇到这个问题的人:

首先,我声明了类VectorTraits,但没有这样定义:

template <typename T, typename = void>
struct VectorTraits;

那个,我提供了一个只在T::dimension &gt; 0时启用的专业化:

template <typename T>
struct VectorTraits<T, std::enable_if_t<(T::dimension > 0)>>
{
  typedef T                       VectorType;
  typedef typename T::ValueType   ValueType;
  static const std::uint16_t      dimension = T::dimension;
};

最后,我使用std::enable_if_t&lt;(sizeof(VectorTraits&lt;T&gt;) &gt; 0)&gt;检查特征是否可用:

template <class T, typename = std::enable_if_t<(sizeof(VectorTraits<T>) > 0)>>
std::ostream& operator << (std::ostream& stream, const T& )
{
    return stream << "Traits. Dimension = " << VectorTraits<T>::dimension << "\n";
}

最终的工作示例在here 或以下可用:

#include <iostream>
#include <type_traits>

// Traits class
template <typename T, typename = void>
struct VectorTraits;

template <typename T>
struct VectorTraits<T, std::enable_if_t<(T::dimension > 0)>>
{
  typedef T                       VectorType;
  typedef typename T::ValueType   ValueType;
  static const std::uint16_t      dimension = T::dimension;
};


// Fake vector class. Defines the required typedef.
struct Vec
{
  typedef float   ValueType;
  static const std::uint16_t dimension = 2;
};

// Fake vector class. Defines the required typedef.
struct VecFake
{
};

template <>
struct VectorTraits<VecFake>
{
  typedef VecFake                       VectorType;
  typedef float   ValueType;
  static const std::uint16_t      dimension = 12;
};

// Streaming operator attempt for classes defining VectorTraits.
template <class T, typename = std::enable_if_t<(sizeof(VectorTraits<T>) > 0)>>
std::ostream& operator << (std::ostream& stream, const T& )
{
    return stream << "Traits. Dimension = " << VectorTraits<T>::dimension << "\n";
}

int main()
{
    std::cout << "Test\n";  // Test
    std::cout << Vec();     // Traits. Dimension = 2
    std::cout << VecFake(); // Traits. Dimension = 12
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-12-08
    • 1970-01-01
    • 1970-01-01
    • 2011-06-03
    • 2010-09-22
    • 1970-01-01
    • 2019-01-30
    相关资源
    最近更新 更多