Vincent Vincent - 9 months ago 36
C++ Question

Overloading linearly: why clang fails where gcc compiles?

Following the question here, I have designed these utilities that run the first function compatible with the given list of arguments. However, it compiles with g++ (under the

-std=c++14
option), but fails with clang. Is it a bug of clang, is it a bug of g++, and how to make it work on both compilers.

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


Error message with
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.

Answer Source

The easiest workaround is to get rid of the offending constexpr variable altogether, which fortunately is trivial – since _findex already returns a std::integral_constant, which encodes the value you want in the type, you can replace the definition of operator() with:

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

Alternatively, you can reimplement _findex such that it only passes around the argument types, and not the arguments themselves, which allows the presently-offending variable to remain:

// 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, a hybrid of both would be best.