解决CRTP功能过载的歧义

lig*_*ulb 6 c++ templates crtp ambiguous c++17

我有几个我想为CRTP基类的派生类工作的函数。问题是,如果我将派生类传递给用于CRTP类的自由函数,则会产生歧义。下面的代码是一个说明这个问题的最小示例:

template<typename T>
struct A{};

struct C : public A<C>{};

struct B{};

template<typename T, typename U>
void fn(const A<T>& a, const A<U>& b) 
{
    std::cout << "LT, RT\n";
}

template<typename T, typename U>
void fn(const T a, const A<U>& b)
{
    std::cout << "L, RT\n";
}

template<typename T, typename U>
void fn(const A<T>& a, const U& b)
{
    std::cout << "LT, R\n";
}

int main()
{
    C a; // if we change C to A<C> everything works fine
    B b;
    fn(a,a); // fails to compile due to ambiguous call
    fn(b,a);
    fn(a,b);
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

理想情况下,我希望此方法适用于派生类,就像我要使用基类一样(不必为基类重新定义所有内容,CRTP习惯用法的全部要点是不必为多个类定义fn )。

Bar*_*rry 6

首先,您需要一个特征以查看某些事物是否类似A。您不能只is_base_of在这里使用,因为您不知道将从哪个 A继承。我们需要使用额外的间接方式:

template <typename T>
auto is_A_impl(A<T> const&) -> std::true_type;
auto is_A_impl(...) -> std::false_type;

template <typename T>
using is_A = decltype(is_A_impl(std::declval<T>()));
Run Code Online (Sandbox Code Playgroud)

现在,我们可以使用此特征来编写我们的三个重载:both A,only left A和only right A

#define REQUIRES(...) std::enable_if_t<(__VA_ARGS__), int> = 0

// both A
template <typename T, typename U, REQUIRES(is_A<T>() && is_A<U>())
void fn(T const&, U const&);

// left A
template <typename T, typename U, REQUIRES(is_A<T>() && !is_A<U>())
void fn(T const&, U const&);

// right A
template <typename T, typename U, REQUIRES(!is_A<T>() && is_A<U>())
void fn(T const&, U const&);
Run Code Online (Sandbox Code Playgroud)

请注意,我只是在接受TU这里,我们不一定要丢脸并丢失信息。


关于C ++ 20中即将出现的概念的一件好事是编写此代码要容易得多。这两个特征现在变成了一个概念:

template <typename T> void is_A_impl(A<T> const&);

template <typename T>
concept ALike = requires(T const& t) { is_A_impl(t); }
Run Code Online (Sandbox Code Playgroud)

以及三个重载:

// both A
template <ALike T, ALike U>
void fn(T const&, U const&);

// left A
template <ALike T, typename U>
void fn(T const&, U const&);

// right A
template <typename T, ALike U>
void fn(T const&, U const&);
Run Code Online (Sandbox Code Playgroud)

语言规则已经强制在可行的情况下,首选“ A重载”。好东西。