why

这篇就是参考链接2的总结,还是从参考链接1中单独拎出来说一下


之前学std::variant 和 std::visit 学到了overloaded这个模板,

#include <variant>
#include <cstdio>
#include <vector>

template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; }; // (1)
template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;  // (2)

using var_t = std::variant<int, const char*>;

int main() {
    std::vector<var_t> vars = {1, 2, "Hello, World!"};

    for (auto& v : vars) {
        std::visit(overloaded {  // (3)
            [](int i) { printf("%d\n", i); },
            [](const char* str) { puts(str); }
        }, v);
    }

    return 0;
}

(2)是c++17引入的新特性,乍一看看不懂,咱们一点一点顺一下

首先,这个overloaded模板就是一个转发继承而来的operator (), 一个粗暴的版本,需要基类实现operator()

struct PrintInt { //(1)
    void operator() (int i) {
        printf("%d\n", i);
    }
};

struct PrintCString { // (2)
    void operator () (const char* str) {
        puts(str);
    }
};

struct Print : PrintInt, PrintCString { // (3)
    using PrintInt::operator();
    using PrintCString::operator();
};

如果写成模板形式,那就是

template <class... Ts> // (1)
struct Print : Ts... {
    using Ts::operator()...;
};

int main() {
    std::vector<var_t> vars = {1, 2, "Hello, World!"};

    for (auto& v : vars) {
        std::visit(Print<PrintCString, PrintInt>{}, v); // (2)
    }

    return 0;
}

注意到这个写法特别重,考虑到开头这个优雅的用法,使用lambda,代码写起来就更难看了

int main() {
    std::vector<var_t> vars = {1, 2, "Hello, World!"};
    auto PrintInt = [](int i) { printf("%d\n", i); }; // (1)
    auto PrintCString = [](const char* str) { puts(str); };

    for (auto& v : vars) {
        std::visit(
            Print<decltype(PrintCString), decltype(PrintInt)>{PrintCString, PrintInt}, // (2)
            v);
    }

    return 0;
}

所以理所当然,推导动作应该放在一个helper函数里, 上面这个调用模式还是很容写出一个推导helper的

template <class... Ts> // (1)
auto MakePrint(Ts... ts) {
    return Print<Ts...>{ts...};
}

int main() {
    std::vector<var_t> vars = {1, 2, "Hello, World!"};

    for (auto& v : vars) {
        std::visit(
            MakePrint( // (2)
                [](const char* str) { puts(str); },
                [](int i) { printf("%d\n", i); }
                ),
            v);
    }

    return 0;
}

这已经和overload非常接近了,回到一开始我们提到的,如何写成开头那个样子呢,这就需要c++

17 的新特性,类模板实参推导, 自定义推导指引,User-defined deduction guides,简单说,就是构造函数能做helper的活(make_tuple, make_pair),只要定义好规则就可以

在c++17中,可以干净的写出

std::tuple t(4, 3, 2.5); // same as auto t = std::make_tuple(4, 3, 2.5);

在tuple中,写好了推导规则

#ifndef _LIBCPP_HAS_NO_DEDUCTION_GUIDES
// NOTE: These are not yet standardized, but are required to simulate the
// implicit deduction guide that should be generated had libc++ declared the
// tuple-like constructors "correctly"
template <class _Alloc, class ..._Args>
tuple(allocator_arg_t, const _Alloc&, tuple<_Args...> const&) -> tuple<_Args...>;
template <class _Alloc, class ..._Args>
tuple(allocator_arg_t, const _Alloc&, tuple<_Args...>&&) -> tuple<_Args...>;
#endif

make_tuple就下岗了

类似的,只要为Print写好推导,就可以省掉MakePrint

#include <variant>
#include <cstdio>
#include <vector>

using var_t = std::variant<int, const char*>;

template <class... Ts>
struct Print : Ts... {
    using Ts::operator()...;
};

template <class...Ts> Print(Ts...) -> Print<Ts...>; // (1)

int main() {
    std::vector<var_t> vars = {1, 2, "Hello, World!"};
    for (auto& v : vars) {
        std::visit(
            Print{ // (2)
                [](const char* str) { puts(str); },
                [](int i) { printf("%d\n", i); }
            },
            v);
    }
    return 0;
}

到此,overloaded trick就解释完了

reference