一个类型特征,标识哪个 class 提供重载决议选择的函数

A type trait identifying which class provides the function selected by overload resolution

考虑以下代码,其中仿函数 derived 继承自两个基 classes base1base2,每个都提供不同的重载:

// Preamble
#include <iostream>
#include <functional>
#include <type_traits>

// Base 1
struct base1 {
    void operator()(int) const {
        std::cout << "void base1::operator()(int) const\n";
    }
    void operator()(double) const {
        std::cout << "void base1::operator()(double) const\n";
    }
    template <class Arg, class... Args>
    void operator()(const Arg&, Args&&...) const {
        std::cout << "void base1::operator()(const Arg&, Args&&...) const\n";
    }
};

// Base 2
struct base2 {
    void operator()(int) {
        std::cout << "void base2::operator()(int)\n";
    }
    void operator()(double) {
        std::cout << "void base2::operator()(double)\n";
    }
};

// Derived
struct derived: base1, base2 {
    using base1::operator();
    using base2::operator();
    void operator()(char) {
        std::cout << "void derived::operator()(char)\n";
    }
};

// Call
template <class F, class... Args>
void call(F&& f, Args&&... args) {
    std::invoke(std::forward<F>(f), std::forward<Args>(args)...);
}

// Main
int main(int argc, char* argv[]) {
    const derived d1;
    derived d2;
    derived d3;
    call(d1, 1);    // void base1::operator()(int) const
    call(d2, 1);    // void base2::operator()(int)
    call(d1, 1, 2); // void base1::operator()(const Arg&, Args&&...) const
    call(d2, 1, 2); // void base1::operator()(const Arg&, Args&&...) const
    call(d3, 'a');  // void derived::operator()(char)
    return 0;
}

结果输出为:

void base1::operator()(int) const
void base2::operator()(int)
void base1::operator()(const Arg&, Args&&...) const
void base1::operator()(const Arg&, Args&&...) const
void derived::operator()(char)

这说明根据参数,选定的重载可以来自 base1base2derived

我的问题是:是否可以通过创建一个类型特征来在编译时做同样的事情,该特征将检测哪个 class 提供了选定的过载?

这将具有以下形式:

template <
    class Base1,   // Possibly cv-ref qualified base1
    class Base2,   // Possibly cv-ref qualified base2
    class Derived, // Possibly cv-ref qualified derived
    class... Args  // Possibly cv-ref qualified args
>
struct overload_origin {
    using base1 = std::decay_t<Base1>;
    using base2 = std::decay_t<Base2>;
    using derived = std::decay_t<Derived>;
    using type = /* base1, base2, or derived * /
};

并且在前面示例代码中的 call 函数中使用时,overload_origin::type 会引用 base1base2base1base1derived 用于示例代码中说明的五个调用。

如何用模板元编程实现这样的事情?

您可以从 derivedbase1 中导出 class。这样,来自 base1 的所有对 operator() 的调用都将是不明确的:

struct derived_check: base1, derived {
    using base1::operator();
    using base2::operator();
};
// Main
int main(int argc, char* argv[]) {
    const derived_check d1;
    derived_check d2;
    derived_check d3;
    call(d1, 1);    // error:ambiguous
    call(d2, 1);    // OK
    call(d1, 1, 2); // error:ambiguous
    call(d2, 1, 2); // error:ambiguous

    return 0;
}

然后您可以使用基本的检测技巧来创建您的检测类型特征。