模板类型擦除

Template type erasure

我想知道是否有一种实用的方法可以使用 C++17 标准编写如下代码:

#include <string>
#include <functional>
#include <unordered_map>

template <class Arg>
struct Foo
{
    using arg_type = Arg;
    using fun_type = std::function< void(Arg&) >;
    fun_type fun;
    
    void call( Arg& arg ) { fun(arg); }
};

struct Bar
{
    using map_type = std::unordered_map<std::string,Foo>; // that's incorrect
    map_type map;
    
    auto& operator[] ( std::string name ) { return map[name]; }
};

在上面的代码中,class Foo 的模板参数对应于一些一元函数的输入类型,returns 没有。具有不同模板类型的 Foo 的不同实例对应于采用不同类型参数的函数。 class Bar 只是为了给这些函数起一个名字,但是很明显,当前映射的声明是不正确的,因为它需要知道 Foo.[=15 的模板类型=]

或者是吗?

不幸的是,通过编译时检查来执行此操作是不可行的。但是,您可以通过运行时检查来提供该功能。

一个地图的值类型只能是一种类型,Foo<T> 是每个 T 的不同类型。然而,我们可以通过给每个 Foo<T> 一个公共基础 class,有一个指向它的指针映射,并使用虚函数将 call() 分派给适当的子 [=] 来解决这个问题28=].

为此,参数的类型也必须始终相同。正如@MSalters 所提到的,std::any 可以提供帮助。

最后,我们可以使用 pimpl 模式包装所有这些,这样看起来只有一个整洁的 Foo 类型:

#include <cassert>
#include <string>
#include <functional>
#include <any>
#include <unordered_map>
#include <memory>

struct Foo {
public:
  template<typename T, typename FunT>
  void set(FunT fun) {
      pimpl_ = std::make_unique<FooImpl<T, FunT>>(std::move(fun));
  }

  // Using operator()() instead of call() makes this a functor, which
  // is a little more flexible.
  void operator()(const std::any& arg) {
      assert(pimpl_);
      pimpl_->call(arg);
  }
  
private:
    struct IFooImpl {
      virtual ~IFooImpl() = default;
      virtual void call( const std::any& arg ) const = 0; 
    };

    template <class Arg, typename FunT>
    struct FooImpl : IFooImpl
    {
        FooImpl(FunT fun) : fun_(std::move(fun)) {}
        
        void call( const std::any& arg ) const override {
            fun_(std::any_cast<Arg>(arg));
        }

    private:
        FunT fun_;
    };

  std::unique_ptr<IFooImpl> pimpl_;
};


// Usage sample
#include <iostream>

void bar(int v) {
    std::cout << "bar called with: " << v << "\n";
}

int main() {
    std::unordered_map<std::string, Foo> table;

    table["aaa"].set<int>(bar);

    // Even works with templates/generic lambdas!
    table["bbb"].set<float>([](auto x) {
        std::cout << "bbb called with " << x << "\n";
    });

    table["aaa"](14);
    table["bbb"](12.0f);
}

see on godbolt