模板类型的重载决策和运算符 << 的问题 - 第 2 部分
problems with overload resolution and operator<< for templated types - part 2
给定以下代码:
#include <string>
#include <type_traits>
#include <sstream>
#include <vector>
#include <iostream>
using namespace std;
namespace has_insertion_operator_impl {
typedef char no;
typedef char yes[2];
struct any_t {
template <typename T>
any_t(T const&);
};
no operator<<(ostream const&, any_t const&);
yes& test(ostream&);
no test(no);
template <typename T>
struct has_insertion_operator {
static ostream& s;
static T const& t;
static bool const value = sizeof(test(s << t)) == sizeof(yes);
};
}
template <typename T>
struct has_insertion_operator : has_insertion_operator_impl::has_insertion_operator<T> {};
template <class T>
typename enable_if<has_insertion_operator<T>::value, string>::type stringify(const T& in) {
stringstream stream;
stream << in;
return stream.str();
}
template <class T>
typename enable_if< ! has_insertion_operator<T>::value, string>::type stringify(const T&) {
return "{?}";
}
// ======= OVERLOADS PROVIDED BY THE USER =======
template<typename T, typename T2>
struct myType { T data; T2 op; };
template<typename T, typename T2>
ostream& operator<<(ostream& s, const myType<T, T2>&) { s << "myType"; return s; }
template<typename T>
ostream& operator<<(ostream& s, const vector<T>&) { s << "vector<T>"; return s; }
template<typename T, typename A>
ostream& operator<<(ostream& s, const vector<T, A>&) { s << "vector<T, A>"; return s; }
int main() {
myType<int, float> a; cout << stringify(a) << endl; // prints "myType"
cout << stringify(6) << endl; // prints "6"
vector<int> v(5); cout << stringify(v) << endl; // prints "{?}"
return 0;
}
为什么模板 myType<>
被字符串化了,而模板化的 vector<>
类型却没有?
对于 vector<>
类型,我得到了默认的 {?}
字符串化,但我显然希望调用底部的重载之一 - 就像 myType<>
编辑:
这里的实际问题是 为什么 has_insertion_operator<vector<int>>
是假的?
我在 C++98 中也需要这个
并且 operator<<
重载应该在 stringify()
之后提供 - 就像 myType<>
这里有两个问题。
首先是 has_insertion_operator<>
谓词有问题。
我把它换成了这个...
template<class T>
struct has_insertion_operator
{
template<class U>
static auto test(U*p) -> decltype((std::declval<std::ostream>() << (*p)), void(), std::true_type());
template<class U>
static auto test(...) -> std::false_type;
static constexpr bool value = decltype(test<T>(0))::value;
};
... 解决了那个问题,并突出显示了下一个(这对你来说可能更严重):
./stringify.cpp:73:12: error: call to function 'operator<<' that is neither visible in the template definition nor found by argument-dependent lookup
stream << in;
^
./stringify.cpp:100:37: note: in instantiation of function template specialization 'stringify<std::__1::vector<int, std::__1::allocator<int> > >' requested here
vector<int> v(5); cout << stringify(v) << endl; // prints "{?}"
^
./stringify.cpp:91:10: note: 'operator<<' should be declared prior to the call site
ostream& operator<<(ostream& s, const vector<T>&) { s << "vector<T>"; return s; }
^
1 error generated.
这是因为模板函数 operator<< <std::vector...>
在定义之前被引用了。
一旦你将它的定义移到 stringify 的定义之上,一切正常。
最终,注:
为 std::vector
重载 operator<<
是一个非常糟糕的主意。以后会让你各种头疼。
But I'm using c++98 and I want users to be able to supply their own overloads [specialisations]
好吧,让我们以简单(且更正确)的方式进行操作,它适用于所有类型的 c++,并且不会因非法重载侵入 std 命名空间而引起头痛:
#include <string>
#include <sstream>
#include <vector>
#include <iostream>
// define a template class emitter which by default simply calls operator<<
template<class T>
struct emitter
{
emitter(const T& v) : _v(v) {}
std::ostream& operator()(std::ostream& os) const {
return os << _v;
}
const T& _v;
};
// emitter<>'s are streamable
template<class T>
std::ostream& operator<<(std::ostream& os, const emitter<T>& e)
{
return e(os);
}
// a factory function to generate the correct emitter
template<class T>
emitter<T> emit(const T& v)
{
return emitter<T>(v);
}
// write one version of stringify in terms of emit<>()
template <class T>
std::string stringify(const T& in) {
std::stringstream stream;
stream << emit(in);
return stream.str();
}
// ======= OVERLOADS PROVIDED BY THE USER =======
template<typename T, typename T2>
struct myType { T data; T2 op; };
// user must provide an emitter for each custom type
template<typename T, typename T2>
struct emitter<myType<T, T2> >
{
typedef myType<T, T2> value_type;
emitter(const value_type& v) : _v(v) {}
std::ostream& operator()(std::ostream& os) const
{
return os << "myType";
}
private:
const value_type& _v;
};
// and for any std:: templates he wants to support
template<class V, class A>
struct emitter<std::vector<V, A> >
{
typedef std::vector<V, A> value_type;
emitter(const value_type& v) : _v(v) {}
std::ostream& operator()(std::ostream& os) const
{
return os << "vector<T, A>";
}
private:
const value_type& _v;
};
// test
int main() {
myType<int, float> a; std::cout << stringify(a) << std::endl; // prints "myType"
std::cout << stringify(6) << std::endl; // prints "6"
std::vector<int> v(5); std::cout << stringify(v) << std::endl; // prints "vector<T, A>"
return 0;
}
我认为问题出在查找上,我会提供我的理解。
当您在 has...
结构参数依赖查找中调用 operator<<
时,由于 myType
位于与重载 operator<<
相同的命名空间中你得到一个正确的字符串。但是,当您尝试输出 vector
时,它会尝试通过相同的参数相关查找规则搜索重载的 operator<<
并且失败,因为 std
命名空间中没有重载的运算符.因此它会回退到从进行调用的名称空间开始的非限定搜索,因此它会找到您的存根 operator<<
因此,为了修复它,您可能已将 operator<<
重载放置到 std
命名空间(标准禁止)或删除您的命名空间 — 它会产生相同的效果。
不过,您不必将所有内容都放在私有命名空间之外。在全局命名空间中做这样的事情就足够了:
typedef char no;
typedef char yes[2];
template<typename T>
no operator<<(ostream const&, T const&);
或者,如果可能的话,最好要求库用户将他们的重载放入他们 类 所在的同一命名空间中。不过,它不适用于 std
成员。
给定以下代码:
#include <string>
#include <type_traits>
#include <sstream>
#include <vector>
#include <iostream>
using namespace std;
namespace has_insertion_operator_impl {
typedef char no;
typedef char yes[2];
struct any_t {
template <typename T>
any_t(T const&);
};
no operator<<(ostream const&, any_t const&);
yes& test(ostream&);
no test(no);
template <typename T>
struct has_insertion_operator {
static ostream& s;
static T const& t;
static bool const value = sizeof(test(s << t)) == sizeof(yes);
};
}
template <typename T>
struct has_insertion_operator : has_insertion_operator_impl::has_insertion_operator<T> {};
template <class T>
typename enable_if<has_insertion_operator<T>::value, string>::type stringify(const T& in) {
stringstream stream;
stream << in;
return stream.str();
}
template <class T>
typename enable_if< ! has_insertion_operator<T>::value, string>::type stringify(const T&) {
return "{?}";
}
// ======= OVERLOADS PROVIDED BY THE USER =======
template<typename T, typename T2>
struct myType { T data; T2 op; };
template<typename T, typename T2>
ostream& operator<<(ostream& s, const myType<T, T2>&) { s << "myType"; return s; }
template<typename T>
ostream& operator<<(ostream& s, const vector<T>&) { s << "vector<T>"; return s; }
template<typename T, typename A>
ostream& operator<<(ostream& s, const vector<T, A>&) { s << "vector<T, A>"; return s; }
int main() {
myType<int, float> a; cout << stringify(a) << endl; // prints "myType"
cout << stringify(6) << endl; // prints "6"
vector<int> v(5); cout << stringify(v) << endl; // prints "{?}"
return 0;
}
为什么模板 myType<>
被字符串化了,而模板化的 vector<>
类型却没有?
对于 vector<>
类型,我得到了默认的 {?}
字符串化,但我显然希望调用底部的重载之一 - 就像 myType<>
编辑:
这里的实际问题是 为什么 has_insertion_operator<vector<int>>
是假的?
我在 C++98 中也需要这个
并且 operator<<
重载应该在 stringify()
之后提供 - 就像 myType<>
这里有两个问题。
首先是 has_insertion_operator<>
谓词有问题。
我把它换成了这个...
template<class T>
struct has_insertion_operator
{
template<class U>
static auto test(U*p) -> decltype((std::declval<std::ostream>() << (*p)), void(), std::true_type());
template<class U>
static auto test(...) -> std::false_type;
static constexpr bool value = decltype(test<T>(0))::value;
};
... 解决了那个问题,并突出显示了下一个(这对你来说可能更严重):
./stringify.cpp:73:12: error: call to function 'operator<<' that is neither visible in the template definition nor found by argument-dependent lookup
stream << in;
^
./stringify.cpp:100:37: note: in instantiation of function template specialization 'stringify<std::__1::vector<int, std::__1::allocator<int> > >' requested here
vector<int> v(5); cout << stringify(v) << endl; // prints "{?}"
^
./stringify.cpp:91:10: note: 'operator<<' should be declared prior to the call site
ostream& operator<<(ostream& s, const vector<T>&) { s << "vector<T>"; return s; }
^
1 error generated.
这是因为模板函数 operator<< <std::vector...>
在定义之前被引用了。
一旦你将它的定义移到 stringify 的定义之上,一切正常。
最终,注:
为 std::vector
重载 operator<<
是一个非常糟糕的主意。以后会让你各种头疼。
But I'm using c++98 and I want users to be able to supply their own overloads [specialisations]
好吧,让我们以简单(且更正确)的方式进行操作,它适用于所有类型的 c++,并且不会因非法重载侵入 std 命名空间而引起头痛:
#include <string>
#include <sstream>
#include <vector>
#include <iostream>
// define a template class emitter which by default simply calls operator<<
template<class T>
struct emitter
{
emitter(const T& v) : _v(v) {}
std::ostream& operator()(std::ostream& os) const {
return os << _v;
}
const T& _v;
};
// emitter<>'s are streamable
template<class T>
std::ostream& operator<<(std::ostream& os, const emitter<T>& e)
{
return e(os);
}
// a factory function to generate the correct emitter
template<class T>
emitter<T> emit(const T& v)
{
return emitter<T>(v);
}
// write one version of stringify in terms of emit<>()
template <class T>
std::string stringify(const T& in) {
std::stringstream stream;
stream << emit(in);
return stream.str();
}
// ======= OVERLOADS PROVIDED BY THE USER =======
template<typename T, typename T2>
struct myType { T data; T2 op; };
// user must provide an emitter for each custom type
template<typename T, typename T2>
struct emitter<myType<T, T2> >
{
typedef myType<T, T2> value_type;
emitter(const value_type& v) : _v(v) {}
std::ostream& operator()(std::ostream& os) const
{
return os << "myType";
}
private:
const value_type& _v;
};
// and for any std:: templates he wants to support
template<class V, class A>
struct emitter<std::vector<V, A> >
{
typedef std::vector<V, A> value_type;
emitter(const value_type& v) : _v(v) {}
std::ostream& operator()(std::ostream& os) const
{
return os << "vector<T, A>";
}
private:
const value_type& _v;
};
// test
int main() {
myType<int, float> a; std::cout << stringify(a) << std::endl; // prints "myType"
std::cout << stringify(6) << std::endl; // prints "6"
std::vector<int> v(5); std::cout << stringify(v) << std::endl; // prints "vector<T, A>"
return 0;
}
我认为问题出在查找上,我会提供我的理解。
当您在 has...
结构参数依赖查找中调用 operator<<
时,由于 myType
位于与重载 operator<<
相同的命名空间中你得到一个正确的字符串。但是,当您尝试输出 vector
时,它会尝试通过相同的参数相关查找规则搜索重载的 operator<<
并且失败,因为 std
命名空间中没有重载的运算符.因此它会回退到从进行调用的名称空间开始的非限定搜索,因此它会找到您的存根 operator<<
因此,为了修复它,您可能已将 operator<<
重载放置到 std
命名空间(标准禁止)或删除您的命名空间 — 它会产生相同的效果。
不过,您不必将所有内容都放在私有命名空间之外。在全局命名空间中做这样的事情就足够了:
typedef char no;
typedef char yes[2];
template<typename T>
no operator<<(ostream const&, T const&);
或者,如果可能的话,最好要求库用户将他们的重载放入他们 类 所在的同一命名空间中。不过,它不适用于 std
成员。