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

1

6

Consider the following code, where a functor derived, inherits from two base classes base1 and base2 each providing different overloads:

// 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;
}

The resulting output is:

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)

which illustrates that depending on the arguments, the selected overload can come from base1, base2 or derived.

My question is: would it be possible to do the same thing at compile-time by creating a type trait which would detect which class has provided the selected overload?

This would have the form:

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 * /
};

and when in use in the call function in the preceding example code, would have overload_origin::type refering to base1, base2, base1, base1, derived for the five calls illustrated in the example code.

How to achieve such a thing with template metaprogramming?

Prindle answered 2/3, 2018 at 7:54 Comment(3)
An interesting question. Maybe the simplest way is to set different tags on the return type. I don't know any better solution.Candie
The core of this problem is "How to programmatically let the compiler tell which overloading is selected?"Candie
Maybe the technique used here could help: https://mcmap.net/q/398239/-overloading-multiple-function-objects-by-referenceCarbonyl
V
3

You can derive a class from derived and base1. This way all calls to operator() coming from base1 will be ambiguous:

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;
}

Then you can use the basic detection tricks to create your detection type trait.

Vazquez answered 2/3, 2018 at 10:28 Comment(1)
I would have never thought about this. This is brilliant!Prindle

© 2022 - 2024 — McMap. All rights reserved.