【问题标题】:How to create a template class that can handle std::set with different type parameters如何创建可以处理具有不同类型参数的 std::set 的模板类
【发布时间】:2020-05-21 17:21:52
【问题描述】:

我需要用 C++ 编写一个实现集合的抽象版本的模板。我找不到关于编译错误的解决方案(或者更糟的是,我真的不明白该怎么做)。

这是我需要编译和运行的主程序的简化版本——也就是说,我不能更改其中的任何内容:

#include <algorithm>
#include <iostream>
#include <iterator>
#include <numeric>
#include <set>
#include <string>
#include "testset.h"

using namespace std;

struct string_size_less
{

  bool operator()( const std::string& a,
                   const std::string& b )
  {
    return a.size() < b.size();
  }

};

int main()
{
    std::set<std::string> msgs;
    msgs.insert("One");
    msgs.insert("Two");
    msgs.insert("Three");

    set_ops<std::string> ops(msgs);
    ops.list();

    std::set<std::string, string_size_less> x;
    x.insert("Hello");
    x.insert("Ciao");

    std::set<std::string, std::greater<std::string> > a;
    a.insert(":-o");

    set_ops<std::string> m(x);
    m.list();

    return 0;
}

我需要编写“set_ops”类(在 testset.h 中)。我剥离了所有不相关的部分(否则有效):

#pragma once

#include <iostream>
#include <set>

using namespace std;

template <class T> class set_ops
{
    private:
        std::set<T> elements;

    public:
        set_ops(std::set<T> initialSet)
        {
            elements = initialSet;
        }

        void list() const;
};

template <class T> void set_ops<T>::list() const
{
    for (typename set<T>::iterator i = elements.begin(); i != elements.end(); ++i) {
        cout << "\t" << *i << endl;
    }
}

当我尝试编译它时,我得到了错误:

In function 'int main()':
error: no matching function for call to 'set_ops<std::__cxx11::basic_string<char> >::set_ops(std::set<std::__cxx11::basic_string<char>, string_size_less>&)'
note: candidate: set_ops<T>::set_ops(std::set<T>) [with T = std::__cxx11::basic_string<char>]|
note:   no known conversion for argument 1 from 'std::set<std::__cxx11::basic_string<char>, string_size_less>' to 'std::set<std::__cxx11::basic_string<char> >'

我尝试了很多东西并试图找到一个很好的例子等,但到目前为止还没有找到。例如,我知道(并尝试过)如果我添加另一个像这样的模板参数:

template <class T, class U = std::less<T> > class set_ops
{
    private:
        std::set<T, U> elements;

    public:
        set_ops(std::set<T, U> initialSet)
        {
            elements = initialSet;
        }

        void list() const;
};

template <class T, class U> void set_ops<T, U>::list() const
{
    for (typename set<T, U>::iterator i = elements.begin(); i != elements.end(); ++i) {
        cout << "\t" << *i << endl;
    }
}

那么如果我明确写:

set_ops<std::string, string_size_less> m(x);

它编译并运行没有错误。但是,同样,我不能更改 main() 中的任何内容,所以这不是一个选项。

如果我保留原始 main() 中的行,即使更改了 testset.h,我也会得到相同的编译错误。

如果有人可以提供帮助,我真的很想了解这里的问题(希望是解决方案)。谢谢!

【问题讨论】:

  • AFAIK 如果不更改 main,这是无法做到的。
  • @NathanOliver 确实可以通过滥用多态性来实现。
  • 是否指定了输出?否则您可以复制内容并更改顺序。

标签: c++ class templates set


【解决方案1】:

这可以通过多态性和模板构造函数来实现。

#include <utility>
#include <memory>
#include <iostream>

template <typename T>
class set_ops
{
private:
    // Interface type.
    class set_ops_iface
    {
    public:
        virtual ~set_ops_iface();

        virtual void list() const = 0;
    };

    // Concrete implementation for a U.
    template <typename U>
    class set_ops_impl : public set_ops_iface
    {
    private:
        U value;

    public:
        explicit set_ops_impl(U);

        virtual void list() const override;
    };

private:
    // Smart pointer to interface type.
    std::unique_ptr<set_ops_iface> impl;

public:
    // Template constructor that can take any kind of container (not just sets)
    template <typename U>
    set_ops(U);

    void list() const;
};

// Template constructor creates a set_ops_impl<U> owned by the interface smart pointer.
template <typename T>
template <typename U>
set_ops<T>::set_ops(U initial) :
    impl{std::make_unique<set_ops_impl<U>>(std::move(initial))} { }

template <typename T>
set_ops<T>::set_ops_iface::~set_ops_iface() { }

template <typename T>
template <typename U>
set_ops<T>::set_ops_impl<U>::set_ops_impl(U initial) :
    value{std::move(initial)} { }

// real list() implementation is in set_ops_impl<U>
template <typename T>
template <typename U>
void set_ops<T>::set_ops_impl<U>::list() const {
    for (auto const & i : value) {
        std::cout << '\t' << i << '\n';
    }
}

// set_ops::list proxies to the polymorphic implementation.
template <typename T>
void set_ops<T>::list() const {
    impl->list();
}

(Demo)

请注意,奇怪的是,我们甚至不使用 T 模板参数。如果main() 不需要它,set_ops 甚至不需要是模板类型。

要添加另一个成员函数,您必须:

  1. 将实现添加到set_ops::set_ops_impl 模板。
  2. 将代理成员添加到set_ops

【讨论】:

  • 这不是要求你有@9​​87654328@std::function成员,每个需要实现的功能一个吗?
  • @NathanOliver 是的。这有点骇人听闻,但满足了问题的限制。如果添加更多成员,那么最好将捕获的initial 分解为set_ops 成员,这可以通过适当销毁捕获的对象以类型安全的方式完成,只需要一些工作完全正确。 .... 另一种方法是让每个 lambda 按值捕获 std::shared_ptr&lt;U&gt;,尽管这样效率不高。
  • 如果可以的话,您可能希望提供该解决方案,因为 OP 的示例已与最基本的要素配对。看起来set_ops需要提供所有的设置接口。
  • @NathanOliver 正在这样做。
  • @NathanOliver 完成
【解决方案2】:

另一个答案有效,因为std::function 使用了一种称为“类型擦除”的技术。如果你不想保留多个std::functions,你可以自己实现类型擦除。

template<typename T>
struct set_ops_erased {
    virtual ~set_ops_erased() = default;
    virtual void list() = 0;
};

template<typename T, typename Comp>
struct set_ops_impl : set_ops_erased<T> {
    std::set<T, Comp> s;
    set_ops_impl(std::set<T, Comp> s) : s(std::move(s)) { }
    void list() override {
        for(auto &x : s) std::cout << "\t" << x << "\n"; // endl is normally not necessary
    }
};

template<typename T>
class set_ops {
     std::unique_ptr<set_ops_erased<T>> ops;
public:
     template<typename Comp>
     set_ops(std::set<T, Comp> s)
     : ops(std::make_unique<set_ops_impl<T, Comp>>(std::move(s)))
     { }

     void list() { ops->list(); }
};

在这种情况下,无需担心编写所有复制/移动构造函数/赋值和析构函数。他们只是做正确的事。因为set_ops_impl 只有一个分配,所以在构造set_ops 时,无需考虑使用“保护”std::unique_ptr 来清理容器。

【讨论】:

    【解决方案3】:

    不更改主文件,但忘记给定集合的初始顺序(因为您没有指定预期输出),您可以这样做:

    template <class T> class set_ops
    {
    private:
        std::set<T> elements;
    
    public:
        template <typename Container>
        set_ops(Container& c) : elements(c.begin(), c.end()) {}
    
        void list() const {
            for (const auto& e : elements) {
                std::cout << "\t" << e << std::endl;
            }  
        }
    };
    
    

    【讨论】:

      猜你喜欢
      • 2020-01-05
      • 2022-01-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多