线性重载:为什么 clang 在 gcc 编译时失败?

Overloading linearly: why clang fails where gcc compiles?

在问题 之后,我设计了这些实用程序 运行 第一个与给定参数列表兼容的函数。但是,它使用 g++ 编译(在 -std=c++14 选项下),但因 clang 而失败。是不是clang的bug,是g++的bug,如何让它在两个编译器上都能正常工作

// Include
#include <tuple>
#include <utility>
#include <iostream>
#include <type_traits>

// Temporary linear overload definition
template <class... F>
class temporary_linear_overload final
{
    // Types
    private:
    struct _default final
    {
        template <class... Args> 
        constexpr void operator()(Args&&...) noexcept
        {
        }
    };
    template <class... G>
    using _self = temporary_linear_overload<G...>;
    using _lvalue_reference = temporary_linear_overload&;
    using _rvalue_reference = temporary_linear_overload&&;
    using _const_lvalue_reference = const temporary_linear_overload&;
    using _const_rvalue_reference = const temporary_linear_overload&&;
    using _ftuple = std::tuple<F..., _default>;
    template <std::size_t N>
    using _ftype = typename std::tuple_element<N, _ftuple>::type;
    template <class T, std::size_t N>
    using _fconstant = std::integral_constant<std::size_t, N>;

    // Lifecycle
    private:
    temporary_linear_overload(_rvalue_reference x) = default;
    temporary_linear_overload(_const_lvalue_reference) = delete;
    temporary_linear_overload& operator=(_rvalue_reference) = delete;
    temporary_linear_overload& operator=(_const_lvalue_reference) = delete;
    template <class... G>
    explicit constexpr temporary_linear_overload(G&&... g) noexcept
    : _f{std::forward<G>(g)..., _default{}}
    {
    }

    // Function index
    template <std::size_t N = 0> 
    static constexpr auto _findex() -> _fconstant<
        decltype(std::declval<_ftype<N>>()()), N
    >
    {
        return _fconstant<void, N>();
    }
    template <std::size_t N = 0, class Arg, class... Args> 
    static constexpr auto _findex(Arg&& arg, Args&&... args) -> _fconstant<
        decltype(std::declval<_ftype<N>>()(
            std::forward<Arg>(arg), 
            std::forward<Args>(args)...)
        ),
        N
    >
    {
        return _fconstant<void, N>();
    }
    template <std::size_t N = 0, class... Args> 
    static constexpr auto _findex(Args&&... args)
    {
        return _findex<N + 1>(std::forward<Args>(args)...);
    }

    // Application
    public:
    template <class... Args> 
    decltype(auto) operator()(Args&&... args) &&
    {
        constexpr std::size_t index = _findex(std::forward<Args>(args)...);
        return std::get<index>(_f)(std::forward<Args>(args)...);
    }

    // Temporary creator
    public:
    template <class... G>
    friend constexpr _self<G...> overload_linearly(G&&... g) noexcept;

    // Data members
    private:
    _ftuple _f;
};

// Overload linearly definition
template <class... F>
constexpr temporary_linear_overload<F...> overload_linearly(F&&... f) noexcept
{
    return temporary_linear_overload<F...>(std::forward<F>(f)...);
}

// Example
int main(int argc, char* argv[])
{
    auto f0 = [](auto i){std::cout<<i<<std::endl;};
    auto f1 = [](auto i, auto j){std::cout<<i<<" "<<j<<std::endl;};
    overload_linearly(f0, f1)(1, 2.0);
    // runs f1 because it's the 1st valid function with the provided arguments
    return 0;
}

clang++ 3.8.0 的错误消息:

overload_linearly_v2.cpp:75:66: error: constexpr variable 'index' must be initialized by a constant expression
        constexpr std::size_t index = _findex(std::forward<Args>(args)...);
                                      ~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~
overload_linearly_v2.cpp:101:30: note: in instantiation of function template specialization 'temporary_linear_overload<(lambda at overload_linearly_v2.cpp:99:15) &, (lambda at overload_linearly_v2.cpp:100:15)
      &>::operator()<int, double>' requested here
    overload_linearly(f0, f1)(1, 2.0);
                             ^
overload_linearly_v2.cpp:76:16: error: no matching function for call to 'get'
        return std::get<index>(_f)(std::forward<Args>(args)...);
               ^~~~~~~~~~~~~~~
/usr/lib/gcc/x86_64-linux-gnu/6.2.0/../../../../include/c++/6.2.0/utility:202:5: note: candidate template ignored: invalid explicitly-specified argument for template parameter '_Int'
    get(std::pair<_Tp1, _Tp2>& __in) noexcept
    ^
/usr/lib/gcc/x86_64-linux-gnu/6.2.0/../../../../include/c++/6.2.0/utility:207:5: note: candidate template ignored: invalid explicitly-specified argument for template parameter '_Int'
    get(std::pair<_Tp1, _Tp2>&& __in) noexcept
    ^
/usr/lib/gcc/x86_64-linux-gnu/6.2.0/../../../../include/c++/6.2.0/utility:212:5: note: candidate template ignored: invalid explicitly-specified argument for template parameter '_Int'
    get(const std::pair<_Tp1, _Tp2>& __in) noexcept
    ^
/usr/lib/gcc/x86_64-linux-gnu/6.2.0/../../../../include/c++/6.2.0/utility:221:5: note: candidate template ignored: invalid explicitly-specified argument for template parameter '_Tp'
    get(pair<_Tp, _Up>& __p) noexcept
    ^
/usr/lib/gcc/x86_64-linux-gnu/6.2.0/../../../../include/c++/6.2.0/utility:226:5: note: candidate template ignored: invalid explicitly-specified argument for template parameter '_Tp'
    get(const pair<_Tp, _Up>& __p) noexcept
    ^
/usr/lib/gcc/x86_64-linux-gnu/6.2.0/../../../../include/c++/6.2.0/utility:231:5: note: candidate template ignored: invalid explicitly-specified argument for template parameter '_Tp'
    get(pair<_Tp, _Up>&& __p) noexcept
    ^
/usr/lib/gcc/x86_64-linux-gnu/6.2.0/../../../../include/c++/6.2.0/utility:236:5: note: candidate template ignored: invalid explicitly-specified argument for template parameter '_Tp'
    get(pair<_Up, _Tp>& __p) noexcept
    ^
/usr/lib/gcc/x86_64-linux-gnu/6.2.0/../../../../include/c++/6.2.0/utility:241:5: note: candidate template ignored: invalid explicitly-specified argument for template parameter '_Tp'
    get(const pair<_Up, _Tp>& __p) noexcept
    ^
/usr/lib/gcc/x86_64-linux-gnu/6.2.0/../../../../include/c++/6.2.0/utility:246:5: note: candidate template ignored: invalid explicitly-specified argument for template parameter '_Tp'
    get(pair<_Up, _Tp>&& __p) noexcept
    ^
/usr/lib/gcc/x86_64-linux-gnu/6.2.0/../../../../include/c++/6.2.0/array:281:5: note: candidate template ignored: invalid explicitly-specified argument for template parameter '_Int'
    get(array<_Tp, _Nm>& __arr) noexcept
    ^
/usr/lib/gcc/x86_64-linux-gnu/6.2.0/../../../../include/c++/6.2.0/array:290:5: note: candidate template ignored: invalid explicitly-specified argument for template parameter '_Int'
    get(array<_Tp, _Nm>&& __arr) noexcept
    ^
/usr/lib/gcc/x86_64-linux-gnu/6.2.0/../../../../include/c++/6.2.0/array:298:5: note: candidate template ignored: invalid explicitly-specified argument for template parameter '_Int'
    get(const array<_Tp, _Nm>& __arr) noexcept
    ^
/usr/lib/gcc/x86_64-linux-gnu/6.2.0/../../../../include/c++/6.2.0/tuple:1254:5: note: candidate template ignored: invalid explicitly-specified argument for template parameter '__i'
    get(tuple<_Elements...>& __t) noexcept
    ^
/usr/lib/gcc/x86_64-linux-gnu/6.2.0/../../../../include/c++/6.2.0/tuple:1260:5: note: candidate template ignored: invalid explicitly-specified argument for template parameter '__i'
    get(const tuple<_Elements...>& __t) noexcept
    ^
/usr/lib/gcc/x86_64-linux-gnu/6.2.0/../../../../include/c++/6.2.0/tuple:1266:5: note: candidate template ignored: invalid explicitly-specified argument for template parameter '__i'
    get(tuple<_Elements...>&& __t) noexcept
    ^
/usr/lib/gcc/x86_64-linux-gnu/6.2.0/../../../../include/c++/6.2.0/tuple:1289:5: note: candidate template ignored: invalid explicitly-specified argument for template parameter '_Tp'
    get(tuple<_Types...>& __t) noexcept
    ^
/usr/lib/gcc/x86_64-linux-gnu/6.2.0/../../../../include/c++/6.2.0/tuple:1295:5: note: candidate template ignored: invalid explicitly-specified argument for template parameter '_Tp'
    get(tuple<_Types...>&& __t) noexcept
    ^
/usr/lib/gcc/x86_64-linux-gnu/6.2.0/../../../../include/c++/6.2.0/tuple:1301:5: note: candidate template ignored: invalid explicitly-specified argument for template parameter '_Tp'
    get(const tuple<_Types...>& __t) noexcept
    ^
2 errors generated.

最简单的解决方法是完全摆脱有问题的 constexpr 变量,幸运的是这很简单——因为 _findex 已经 returns 一个 std::integral_constant,它编码了type 中你想要的值,你可以将 operator() 的定义替换为:

template <class... Args> 
decltype(auto) operator()(Args&&... args) &&
{
    using index = decltype(_findex(std::forward<Args>(args)...));
    return std::get<index::value>(_f)(std::forward<Args>(args)...);
}

Online Demo

或者,您可以重新实现 _findex,使其仅传递参数 types,而不传递参数本身,这样可以保留当前有问题的变量:

// Function index
template <std::size_t N, class... Args>
static constexpr auto _findex(int) -> _fconstant<
    decltype(std::declval<_ftype<N>>()(std::declval<Args>()...)),
    N
>
{
    return {};
}

template <std::size_t N, class... Args>
static constexpr auto _findex(long)
{
    return _findex<N + 1, Args...>(0);
}

// Application
template <class... Args> 
decltype(auto) operator()(Args&&... args) &&
{
    constexpr std::size_t index = _findex<0, Args&&...>(0);
    return std::get<index>(_f)(std::forward<Args>(args)...);
}

Online Demo

IMO,最好将两者结合起来。