在编译时使用C ++对成员函数进行别名

时间:2017-09-06 03:12:04

标签: c++ templates metaprogramming

假设我有一些通用代码,我希望将其重用于实现相同底层功能的多个类,但具有不同成员函数名的接口。例如,如果基础类具有erase成员函数,则以下代码将起作用,例如, std::setstd::unordered_set

template <typename T>
static std::chrono::duration<double> set_insert_time(const typename T::value_type &v) {
    T set;
    std::chrono::time_point<std::chrono::high_resolution_clock> start, end;
    start = std::chrono::high_resolution_clock::now();
    set.erase(v);
    end = std::chrono::high_resolution_clock::now();
    return end - start;
}

但是,现在我希望这个功能可用于例如tbb::concurrent_unordered_set,它提供了一个名为unsafe_erase的函数。

我最初的方法是利用具有部分模板特化的类型特征,通过定义以下内容,然后调用set_ops<T>::erase(set, v)。不幸的是,这并没有编译,因为  tbb::concurrent_unordered_set是模板类,而不是类型。我还尝试使用键类型的第二个模板参数扩展类型特征,但这无法编译,因为T不是std::mem_fn(&T<U>::erase)中的模板。

template <typename T>
struct set_ops {
  constexpr static auto erase = std::mem_fn(&T::erase);
};

template <>
struct set_ops<tbb::concurrent_unordered_set> {
  constexpr static auto erase = std::mem_fn(&T::unsafe_erase);
};

我还尝试使用函数模板包装成员函数,如下所示。这似乎是编译的,但由于对例如未定义的引用而无法链接。 decltype ((({parm#1}.erase)({parm#2})),((bool)())) erase<std::set<unsigned int, std::less<unsigned int>, std::allocator<unsigned int> > >(std::set<unsigned int, std::less<unsigned int>, std::allocator<unsigned int> >&, std::set<unsigned int, std::less<unsigned int>, std::allocator<unsigned int> >::key_type const&)

template <typename T>
constexpr auto set_erase(T& s, const typename T::key_type &v) -> decltype(s.erase(v), bool());
template <typename T>
constexpr auto set_erase(T& s, const typename T::key_type &v) -> decltype(s.unsafe_erase(v), bool());

我应该如何在编译时执行此别名?我知道我可以提供一个继承自每个底层类的抽象接口的实现,或者使用指向成员函数的指针,但我想避免任何运行时开销。

4 个答案:

答案 0 :(得分:3)

您可以在助手结构中提供简单的包装函数以及部分特化:

template <typename T>
struct set_ops {
  static auto erase(T& t, const T::value_type& obj) {
    return t.erase(obj);
  }
};

template <typename... T>
struct set_ops<tbb::concurrent_unordered_set<T...>> {
  using set_type = tbb::concurrent_unordered_set<T...>;
  static auto erase(set_type& t, const typename set_type::value_type& obj) {
    return t.unsafe_erase(obj);
  }
};

然后你的set_inert_time函数看起来像这样:

template <typename T>
static std::chrono::duration<double> set_insert_time(const typename T::value_type &v) {
    T set;
    std::chrono::time_point<std::chrono::high_resolution_clock> start, end;
    start = std::chrono::high_resolution_clock::now();
    set_ops<T>::erase(set, v);
    end = std::chrono::high_resolution_clock::now();
    return end - start;
}

这避免了所有使用成员函数指针的麻烦,并且在编译时使所有内容都可以很好地解析。

答案 1 :(得分:1)

如果您的编译器已经实现了概念TS,那么它就可以这么简单:

template <typename T>
static std::chrono::duration<double> set_insert_time(const typename T::value_type &v) {
    T set;
    std::chrono::time_point<std::chrono::high_resolution_clock> start, end;
    start = std::chrono::high_resolution_clock::now();
    if constexpr(requires{set.erase(v);}) set.erase(v);
    else set.unsafe_erase(v);
    end = std::chrono::high_resolution_clock::now();
    return end - start;
}

通过在实例化模板函数之前检查概念,你可以做得更好。

答案 2 :(得分:1)

您可能只是对某些SFINAE使用重载:

template <typename F>
static std::chrono::duration<double> timed_func(F&& f) {
    std::chrono::time_point<std::chrono::high_resolution_clock> start, end;
    start = std::chrono::high_resolution_clock::now();
    std::forward<F>(f)();
    end = std::chrono::high_resolution_clock::now();
    return end - start;
}


template <typename T>
static auto set_insert_time(const typename T::value_type &v)
-> decltype(
    static_cast<void>(std::declval<T&>().erase(v)),
    std::declval<std::chrono::duration<double>>())
{
    T set;
    return timed_func([&](){ set.erase(v); });
}

template <typename T>
static auto set_insert_time(const typename T::value_type &v)
-> decltype(
    static_cast<void>(std::declval<T&>().unsafe_erase(v)),
    std::declval<std::chrono::duration<double>>())
{
    T set;
    return timed_func([&](){ set.unsafe_erase(v); });
}

答案 3 :(得分:0)

只要成员函数具有统一的签名,就可以使用指向成员函数的指针,作为非类型模板参数或编译时constexpr,但语法可能是......你知道,它是C ++反正。

以下代码为gcc 7.1编译。我没有tbb库来测试它,但它应该适用于其他编译器。

// traits declaration
template <typename T> struct set_ops;

// this template use a non type template parameter, assuming the member
// function's signature is like this: (pseudo code)
// template <typename T> struct some_set_implementation<T>
// { iterator_type erase(const value_type &); };
template <typename T, typename T::iterator_type (T::*memfn)(const typename T::value_type &) = set_ops<T>::erase>
static std::chrono::duration<double> set_insert_time(const typename T::value_type &v) {
    T set;
    std::chrono::time_point<std::chrono::high_resolution_clock> start, end;
    start = std::chrono::high_resolution_clock::now();
    (set.*memfn)(v);
    end = std::chrono::high_resolution_clock::now();
    return end - start;
}

// this code use constexpr
template <typename T>
static std::chrono::duration<double> set_insert_time(const typename T::value_type &v) {
    T set;
    constexpr auto memfn = set_ops<T>::erase;
    std::chrono::time_point<std::chrono::high_resolution_clock> start, end;
    start = std::chrono::high_resolution_clock::now();
    (set.*memfn)(v);
    end = std::chrono::high_resolution_clock::now();
    return end - start;
}

// here goes specilizations for the type trait
template <typename T>
struct set_ops<concurrent_unordered_set<T>> {
    static constexpr auto erase = &concurrent_unordered_set<T>::unsafe_erase;
};
template <typename T, template <typename> class CONTAINER>
struct set_ops<CONTAINER<T>> {
    static constexpr auto erase = &CONTAINER<T>::erase;
};

编辑:

忘记成员函数指针疯狂。

见迈尔斯的回答。一个非成员函数包装器肯定是一种更清洁的方式。