应用 SFINAE 检查是否为 T 定义了特征
Apply SFINAE to check if a trait is defined for T
我目前正在研究一个小的数学向量 class。
我希望两个向量 class、Vector2
和 Vector3
可以从一个向量构造到另一个向量。
例如:
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;
};
此表单将允许用户在现有向量 classes(例如 glm::vec2)和我的 classes 之间创建 link。然后可以从 glm::vec2.
创建 Vector2
此外,这项技术可以让我为所有 classes 编写一个通用的流式运算符,使用 SFINAE 定义 VectorTraits
。
这是我的问题,我无法定义 operator<<
,因此当 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
class,它必须专门用于每个 Vector
。但我也想提供一种方法,使 "automatically" 成为 Vector
并定义了一些 typedef
。
我不明白为什么在展示版本中,我的运算符没有被编译器保留。我也尝试了一些变体,但它总是要么匹配所有,要么什么都不匹配。
由于以下原因,您的特定代码不起作用。
对于您的载体,std::enable_if_t<(VectorTraits<T>::dimension > 0)>
是一些 类型 。因此,您将 operator<<
声明为模板,其中第二个参数是该类型 的 值。对于 Dummy
(以及 char*
)没有这样的类型,因此 SFINAE 排除了这一点。但是对于 Vec
你有 std::enable_if_t<(VectorTraits<T>::dimension > 0)>
是一些 type,并且编译器期望模板参数是一些 value那种类型的。当然,它无法找出该值应该是多少。
一个问题是您没有为 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<T>
实例化中,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
检查中,这样如果您多次需要此类检查,您就可以更新一个点。
您可以向您的 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>;
Jarod42 提供的解决方案在 Visual Studio 上不起作用,所以我设法找到了另一个。我 post 它可以帮助有一天或另一天遇到这个问题的人:
首先,我声明了 class VectorTraits
而没有这样定义它:
template <typename T, typename = void>
struct VectorTraits;
我提供了一个专门化,只有在 T::dimension > 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<(sizeof(VectorTraits<T>) > 0)>
:
检查特征是否可用
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
}
我目前正在研究一个小的数学向量 class。
我希望两个向量 class、Vector2
和 Vector3
可以从一个向量构造到另一个向量。
例如:
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;
};
此表单将允许用户在现有向量 classes(例如 glm::vec2)和我的 classes 之间创建 link。然后可以从 glm::vec2.
创建 Vector2此外,这项技术可以让我为所有 classes 编写一个通用的流式运算符,使用 SFINAE 定义 VectorTraits
。
这是我的问题,我无法定义 operator<<
,因此当 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
class,它必须专门用于每个 Vector
。但我也想提供一种方法,使 "automatically" 成为 Vector
并定义了一些 typedef
。
我不明白为什么在展示版本中,我的运算符没有被编译器保留。我也尝试了一些变体,但它总是要么匹配所有,要么什么都不匹配。
由于以下原因,您的特定代码不起作用。
对于您的载体,std::enable_if_t<(VectorTraits<T>::dimension > 0)>
是一些 类型 。因此,您将 operator<<
声明为模板,其中第二个参数是该类型 的 值。对于 Dummy
(以及 char*
)没有这样的类型,因此 SFINAE 排除了这一点。但是对于 Vec
你有 std::enable_if_t<(VectorTraits<T>::dimension > 0)>
是一些 type,并且编译器期望模板参数是一些 value那种类型的。当然,它无法找出该值应该是多少。
一个问题是您没有为 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<T>
实例化中,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
检查中,这样如果您多次需要此类检查,您就可以更新一个点。
您可以向您的 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>;
Jarod42 提供的解决方案在 Visual Studio 上不起作用,所以我设法找到了另一个。我 post 它可以帮助有一天或另一天遇到这个问题的人:
首先,我声明了 class VectorTraits
而没有这样定义它:
template <typename T, typename = void>
struct VectorTraits;
我提供了一个专门化,只有在 T::dimension > 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<(sizeof(VectorTraits<T>) > 0)>
:
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
}