【发布时间】:2021-07-25 23:22:25
【问题描述】:
我有一个非常具体的情况,我将一堆数据提供给一个类似哈希器的类。特别是,我使用的一种数据类型有一个成员,其类型取决于超类型的类型参数。长话短说,这里有一段代码说明了这种行为:
#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 上的断言失败。
关于我应该如何解决这个问题的任何想法?
【问题讨论】:
-
在
template <class T> auto operator<<(Catcher& c, const typename ValueOrRange<T>::Range& r),T是non-deduced context。只能使用显式指定的模板参数调用此重载,例如operator<< <float> (c, v.range);T不能从参数中自动推导出来。这就是为什么c << v.range会陷入包罗万象的重载。 -
@IgorTandetnik 我明白了。但是,在字符串的特定情况下,范围参数只是一个向量,预计将通过基于范围的重载进行处理,所以我想必须在函数本身内进行区分。谢谢你的解释。
标签: c++ templates gcc visual-c++ operator-overloading