使用模板化重载的编译器差异
Compiler differences with use of templated overloads
我有一个非常特殊的情况,我正在将一堆数据提供给类似哈希的 class。特别是,我使用的一种数据类型有一个成员,其类型取决于超类型的类型参数。长话短说,这里有一段代码可以说明这种行为:
#include <assert.h>
#include <iostream>
#include <string>
#include <type_traits>
#include <utility>
#include <vector>
// Some dummy priority structs to select overloads
struct priority0 { };
struct priority1 : priority0 { };
// This is the hasher-like function
struct Catcher
{
// Ideally we feed everything to this object through here
template <typename T> Catcher& operator<<(const T& v)
{
add(v, priority1{}); // always attempt to call the highest-priority overload
return *this;
}
// For floating-point data types
template <typename T> auto add(const T& v, priority1) -> std::enable_if_t<std::is_floating_point_v<T>, void>
{
std::cout << "caught float/double : " << v << std::endl;
}
// For ranges
template <class T> auto add(const T& range, priority1) -> decltype(begin(range), end(range), void())
{
for(auto const& v : range)
*this << v;
}
// For chars
void add(char c, priority1)
{
std::cout << c;
std::cout.flush();
}
// When everything else fails ; ideally should never happen
template <typename T> void add(const T& v, priority0)
{
assert(false && "should never happen");
}
};
// The one data type. Notice how the primary template and the
// specialization have a `range` member of different types
template <class T> struct ValueOrRange
{
struct Range
{
T min;
T max;
};
Range range;
T value;
};
template <> struct ValueOrRange<std::string>
{
std::vector<std::string> range;
std::string value;
};
// Overload operator<< for Catcher outside of the
// class to allow for processing of the new data type
// Also overload that for `ValueOrRange<T>::Range`. SFINAE should make sure
// that this behaves correctly (?)
template <class T> Catcher& operator<<(Catcher& c, const typename ValueOrRange<T>::Range& r)
{
return c << r.min << r.max;
}
template <class T> Catcher& operator<<(Catcher& c, const ValueOrRange<T>& v)
{
return c << v.range << v.value;
}
int main(int argc, char *argv[])
{
ValueOrRange<std::string> vor1{{}, "bleh"};
ValueOrRange<float> vor2{{0.f, 1.f}, 0.5f};
Catcher c;
c << vor1; // works fine, displays "bleh"
c << vor2; // fails the assert in Catcher::add(const T&, priority0) with T = ValueOrRange<float>::Range
return 0;
}
虽然第 c << vor1
行通过各种重载得到正确解析并具有预期效果,但第二行 c << vor2
断言失败。
- 我想要发生的事情:
c << vor2
调用 Catcher& operator<<(Catcher& s, const ValueOrRange<float>& v)
,后者又调用 Catcher& operator<<(Catcher& s, const typename ValueOrRange<float>::Range& r)
- 发生了什么:调用的不是
Catcher& operator<<(Catcher& s, const typename ValueOrRange<float>::Range& r)
,而是 Catcher& Catcher::operator<<(const T& v)
和 T = typename ValueOrRange<float>::Range
,因此断言失败。
值得注意的是,同一代码对 MSVC 具有预期效果,但在 GCC 上断言失败。
知道我应该如何解决这个问题吗?
感谢 Igor Tandetnik 的反馈,我摆脱了 ::Range
特定的过载,只是去检查 std::is_same_v<T, std::string>
。模块化比我想要的要少一些,但它可以解决问题。
// A single function will do the trick
template <class T> Catcher& operator<<(Catcher& c, const ValueOrRange<T>& v)
{
if constexpr (std::is_same_v<T, std::string>)
c << v.range;
else
c << v.range.min << v.range.max;
return c << v.value;
}
在Catcher& operator<<(Catcher& c, const typename ValueOrRange<T>::Range& r)
,T
在不可推。
一种解决方法是 friend
函数:
template <class T> struct ValueOrRange
{
struct Range
{
T min;
T max;
friend Catcher& operator<<(Catcher& c, const Range& r)
{
return c << r.min << r.max;
}
};
Range range;
T value;
};
我有一个非常特殊的情况,我正在将一堆数据提供给类似哈希的 class。特别是,我使用的一种数据类型有一个成员,其类型取决于超类型的类型参数。长话短说,这里有一段代码可以说明这种行为:
#include <assert.h>
#include <iostream>
#include <string>
#include <type_traits>
#include <utility>
#include <vector>
// Some dummy priority structs to select overloads
struct priority0 { };
struct priority1 : priority0 { };
// This is the hasher-like function
struct Catcher
{
// Ideally we feed everything to this object through here
template <typename T> Catcher& operator<<(const T& v)
{
add(v, priority1{}); // always attempt to call the highest-priority overload
return *this;
}
// For floating-point data types
template <typename T> auto add(const T& v, priority1) -> std::enable_if_t<std::is_floating_point_v<T>, void>
{
std::cout << "caught float/double : " << v << std::endl;
}
// For ranges
template <class T> auto add(const T& range, priority1) -> decltype(begin(range), end(range), void())
{
for(auto const& v : range)
*this << v;
}
// For chars
void add(char c, priority1)
{
std::cout << c;
std::cout.flush();
}
// When everything else fails ; ideally should never happen
template <typename T> void add(const T& v, priority0)
{
assert(false && "should never happen");
}
};
// The one data type. Notice how the primary template and the
// specialization have a `range` member of different types
template <class T> struct ValueOrRange
{
struct Range
{
T min;
T max;
};
Range range;
T value;
};
template <> struct ValueOrRange<std::string>
{
std::vector<std::string> range;
std::string value;
};
// Overload operator<< for Catcher outside of the
// class to allow for processing of the new data type
// Also overload that for `ValueOrRange<T>::Range`. SFINAE should make sure
// that this behaves correctly (?)
template <class T> Catcher& operator<<(Catcher& c, const typename ValueOrRange<T>::Range& r)
{
return c << r.min << r.max;
}
template <class T> Catcher& operator<<(Catcher& c, const ValueOrRange<T>& v)
{
return c << v.range << v.value;
}
int main(int argc, char *argv[])
{
ValueOrRange<std::string> vor1{{}, "bleh"};
ValueOrRange<float> vor2{{0.f, 1.f}, 0.5f};
Catcher c;
c << vor1; // works fine, displays "bleh"
c << vor2; // fails the assert in Catcher::add(const T&, priority0) with T = ValueOrRange<float>::Range
return 0;
}
虽然第 c << vor1
行通过各种重载得到正确解析并具有预期效果,但第二行 c << vor2
断言失败。
- 我想要发生的事情:
c << vor2
调用Catcher& operator<<(Catcher& s, const ValueOrRange<float>& v)
,后者又调用Catcher& operator<<(Catcher& s, const typename ValueOrRange<float>::Range& r)
- 发生了什么:调用的不是
Catcher& operator<<(Catcher& s, const typename ValueOrRange<float>::Range& r)
,而是Catcher& Catcher::operator<<(const T& v)
和T = typename ValueOrRange<float>::Range
,因此断言失败。
值得注意的是,同一代码对 MSVC 具有预期效果,但在 GCC 上断言失败。
知道我应该如何解决这个问题吗?
感谢 Igor Tandetnik 的反馈,我摆脱了 ::Range
特定的过载,只是去检查 std::is_same_v<T, std::string>
。模块化比我想要的要少一些,但它可以解决问题。
// A single function will do the trick
template <class T> Catcher& operator<<(Catcher& c, const ValueOrRange<T>& v)
{
if constexpr (std::is_same_v<T, std::string>)
c << v.range;
else
c << v.range.min << v.range.max;
return c << v.value;
}
在Catcher& operator<<(Catcher& c, const typename ValueOrRange<T>::Range& r)
,T
在不可推。
一种解决方法是 friend
函数:
template <class T> struct ValueOrRange
{
struct Range
{
T min;
T max;
friend Catcher& operator<<(Catcher& c, const Range& r)
{
return c << r.min << r.max;
}
};
Range range;
T value;
};