微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

<< 运算符模板的实现 // C++

如何解决<< 运算符模板的实现 // C++

我想在 C++ 中制作一个

template <typename T>
ostream& operator<<(ostream& os,T something)
{
    os << something.begin() << something.end();
    return os;
}

它并没有真正起作用,我认为有经验的 C++ 程序员可以解释我为什么。

预先感谢您对该问题的任何回答。

解决方法

您的重载将匹配几乎所有导致 operator<< 已经有重载的类型的歧义。

我怀疑您想在此处打印容器中的所有元素:os << something.begin() << something.end();。这将不起作用,因为 begin()end() 返回迭代器。你可以取消引用它们

if(something.begin() != something.end())
    os << *something.begin() << *std::prev(something.end());

但您只会打印第一个和最后一个元素。这将打印所有这些:

for(const auto& v : something) os << v;

为了解决歧义问题,您可以使用模板模板参数并为您想要支持的容器启用 operator<< 重载。

示例:

#include <deque>
#include <iostream>
#include <iterator>
#include <list>
#include <map>
#include <type_traits>
#include <vector>

// helper trait - add containers you'd like to support to the list
template <typename T> struct is_container : std::false_type {};
template <typename... Ts> struct is_container<std::vector<Ts...>> : std::true_type{};
template <typename... Ts> struct is_container<std::list<Ts...>> : std::true_type{};
template <typename... Ts> struct is_container<std::deque<Ts...>> : std::true_type{};
template <typename... Ts> struct is_container<std::map<Ts...>> : std::true_type{};

// C is the container template,like std::vector
// Ts... are the template parameters used to create the container.
template <template <typename...> class C,typename... Ts>
// only enable this for the containers you want to support
typename std::enable_if<is_container<C<Ts...>>::value,std::ostream&>::type
operator<<(std::ostream& os,const C<Ts...>& something) {
    auto it = something.begin();
    auto end = something.end();
    if(it != end) {
        os << *it;
        for(++it; it != end; ++it) {
            os << ',' << *it;
        }
    }
    return os;
}

另一种可能是使其通用,但禁用已支持流式传输的类型的重载。

#include <iostream>
#include <iterator>
#include <type_traits>

// A helper trait to check if the type already supports streaming to avoid adding
// an overload for std::string,std::filesystem::path etc.
template<typename T>
class is_streamable {
    template<typename TT>
    static auto test(int) ->
    decltype( std::declval<std::ostream&>() << std::declval<TT>(),std::true_type() );

    template<typename>
    static auto test(...) -> std::false_type;

public:
    static constexpr bool value = decltype(test<T>(0))::value;
};

template <typename T,typename U = decltype(*std::begin(std::declval<T>())),// must have begin
    typename V = decltype(*std::end(std::declval<T>()))    // must have end
>
// Only enable the overload for types not already streamable
typename std::enable_if<not is_streamable<T>::value,const T& something) {
    auto it = std::begin(something);
    auto end = std::end(something);
    if(it != end) {
        os << *it;
        for(++it; it != end; ++it) {
            os << ',' << *it;
        }
    }
    return os;
}

注意:最后一个示例适用于 clang++MSVC,但无法在 g++ 中编译(超出递归深度)。

对于具有本身不可流式传输的 value_type 的容器,例如 std::pair<const Key,T> 中的 std::map,您需要添加单独的重载。这需要在上述任何模板之前声明:

template <typename Key,typename T>
std::ostream &operator<<(std::ostream &os,const std::pair<const Key,T>& p) {
    return os << p.first << ',' << p.second;
}
,

您的代码有正确的想法,但缺少一些东西。

template <typename T>
ostream& operator<<(ostream& os,T something)
{
    os << something.begin() << something.end();
    return os;
}

可迭代容器(如 std::map 等)应该通过迭代它们的所有元素并逐一输出每个元素来输出。在这里,您只输出开始和结束的迭代器,它们与元素本身不同。

我们可以改为使用 *it 从容器中的迭代器中获取元素。因此,下面的代码将输出类型为 T 的标准容器中的所有元素。我还包括一些额外的漂亮印刷。

template <typename T>
std::ostream &operator<<(std::ostream &os,const T &o) {
    auto it = o.begin();
    os << "{" << *it;
    for (it++; it != o.end(); it++) {
        os << "," << *it;
    }
    return os << "}";
}

如果我们只是使用

template <typename T>

在此函数声明之前,它将与现有的 << 运算符声明冲突。也就是说,当我们编写std::cout << std::string("hello world");时,它是调用了我们的函数实现,还是调用了<string>中的函数实现?当然,如果可用,我们希望使用标准的 operator<< 实现。我们通过限制模板来做到这一点,使其仅适用于具有 begin()end() 成员的标准容器,而不适用于具有 std::string 和 {{1} 的 begin() } 但也有我们想要使用的现有 end() 实现。

operator<<

第二个 template <typename T,typename std::enable_if<is_iterable<T>::value,bool>::type = 0,typename std::enable_if<!std::is_same<T,std::string>::value,bool>::type = 0> 很简单:模板应该涵盖类型,只要它们不是 std::enable_if。第一个 std::string 检查类型 std::enable_if 是否可迭代。我们需要自己进行这项检查。

T

template <typename T> class is_iterable { private: typedef char True[1]; typedef char False[2]; template <typename Q,typename std::enable_if< std::is_same<decltype(std::declval<const Q &>().begin()),decltype(std::declval<const Q &>().begin())>::value,char>::type = 0> static True &test(char); template <typename...> static False &test(...); public: static bool const value = sizeof(test<T>(0)) == sizeof(True); }; 有两个版本的函数 is_iterable。如果 testbegin() 存在于类型 end() 上,则启用第一个版本,并且它们的返回类型相同(有更精确的检查方法,但现在就足够了)。否则调用第二个版本。两个版本的返回类型不同,通过检查返回类型的大小,我们可以设置T,当且仅当valueiterable时,它才会是true (在我们的例子中,如果 T 定义了 Tbegin() 并且它们的返回类型是相同的)。

最后,我们注意到 end() 的元素实际上是 std::map<T1,T2> 类型,因此我们需要额外为模板对重载 std::pair<T1,T2>

operator<<

综合起来,我们可以试试这个。请注意,它甚至适用于嵌套的 iterator 类型,例如 template <typename T1,typename T2> std::ostream &operator<<(std::ostream &os,const std::pair<T1,T2> &o) { return os << "(" << o.first << "," << o.second << ")"; }

listUnorderedSetTest

输出:

#include <iostream>
#include <list>
#include <map>
#include <set>
#include <type_traits>
#include <unordered_set>
#include <vector>

template <typename T>
class is_iterable {
    private:
    typedef char True[1];
    typedef char False[2];

    template <typename Q,char>::type = 0>
    static True &test(char);

    template <typename...>
    static False &test(...);

    public:
    static bool const value = sizeof(test<T>(0)) == sizeof(True);
};

template <typename T1," << o.second << ")";
}

template <typename T,bool>::type = 0>
std::ostream &operator<<(std::ostream &os," << *it;
    }
    return os << "}";
}

int main() {
    std::vector<std::string> vectorTest{"hello","world","!"};
    std::cout << vectorTest << std::endl;

    std::set<const char *> setTest{"does","this","set","work","?"};
    std::cout << setTest << std::endl;

    std::map<std::string,std::size_t> mapTest{
        {"bob",100},{"alice",16384},{"xavier",216}};
    std::cout << mapTest << std::endl;

    std::list<std::unordered_set<std::string>> listUnorderedSetTest{
        {"alice","abraham","aria"},{"carl","crystal","ciri"},{"november","nathaniel"}};
    std::cout << listUnorderedSetTest << std::endl;
    return 0;
}

Templated check for the existence of a class member function? 上还有很多其他相关讨论,您可能会发现它们很有帮助。这个答案的缺点是检查 {hello,world,!} {does,this,set,work,?} {(alice,16384),(bob,100),(xavier,216)} {{alice,abraham,aria},{carl,crystal,ciri},{november,nathaniel}} 而不是检查现有的 std::string 实现,我认为可以通过更多的工作来解决 operator<< 的类型检查。>

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。