Search code examples
c++c++14sfinaeresult-of

Unexpected SFINAE failure using std::result_of


In c++14, std::result_of is supposed to result in SFINAE if the expression is ill-formed*. Instead I'm getting a compilation error ("invalid operands to binary expression") on my final case below (i.e. letting the compiler deduce type for std::plus<>). The first three cases work as expected. Code and results shown below.

#include <boost/mpl/placeholders.hpp>
#include <boost/mpl/apply.hpp>
#include <iostream>
#include <utility>
#include <stdexcept>
#include <functional>

namespace mpl = boost::mpl;


template <typename OP, typename T, typename OP_T = typename mpl::apply<OP, T>::type>
struct apply_if_ok: OP_T {

    template <typename...Args, typename R = std::result_of_t<OP_T(Args...)>>
    R operator()(Args&&...args) const {
        return static_cast<OP_T>(*this)(std::forward<Args>(args)...);
    }
    template <typename...Args>
    auto operator()(...) const {
        // throw std::runtime_error("Invalid arguments");
        return "Invalid arguments";
    }
};


int main() {
    using OP = std::plus<mpl::_>;
    int i = 3;

    auto n1 = apply_if_ok<OP, void>()(1, 2);
    std::cout << "plus (1, 2) = " << n1 << std::endl;

    auto n2 = apply_if_ok<OP, void>()(1, &i);
    std::cout << "plus (1, *) = " << n2 << std::endl;

    auto n3 = apply_if_ok<OP, int>()(&i, &i);
    std::cout << "plus (*, *) = " << n3 << std::endl;

    // auto n4 = apply_if_ok<OP, void>()(&i, &i);
    // std::cout << "plus (*, *) = " << n4 << std::endl;
}

The output:

% c++ -std=c++1y -g -pedantic    sfinae_result_of.cc   -o sfinae_result_of
./sfinae_result_of
plus (1, 2) = 3
plus (1, *) = 0x7fff5e782a80
plus (*, *) = Invalid arguments

% c++ -v
Apple LLVM version 6.0 (clang-600.0.56) (based on LLVM 3.5svn)
Target: x86_64-apple-darwin14.1.0
Thread model: posix

Any pointers on what I'm doing wrong would be appreciated!

Thanks.

  • From cppreference.com. I think relevant standard reference is 20.10.7.6, comments on last table entry.

Solution

  • This is caused by a bug in libc++, which I actually just reported a few days ago. (Update: The bug has been fixed in trunk.)

    The problem is that their "diamond functor" implementation is non-conforming. For instance, they implemented std::plus<void>::operator() as follows:

    template <class _T1, class _T2>
    _LIBCPP_CONSTEXPR_AFTER_CXX11 _LIBCPP_INLINE_VISIBILITY
    auto operator()(_T1&& __t, _T2&& __u) const
        { return _VSTD::forward<_T1>(__t) + _VSTD::forward<_T2>(__u); }
    

    when it should be

    template <class _T1, class _T2>
    _LIBCPP_CONSTEXPR_AFTER_CXX11 _LIBCPP_INLINE_VISIBILITY
    auto operator()(_T1&& __t, _T2&& __u) const
        -> decltype(_VSTD::forward<_T1>(__t) + _VSTD::forward<_T2>(__u))
        { return _VSTD::forward<_T1>(__t) + _VSTD::forward<_T2>(__u); }
    

    The missing trailing-return-type means two things:

    1. They are no longer "perfectly returning"; instead, the return type is deduced using the rules for auto, essentially causing it to be decayed. The trailing return type, when the expression in it is well-formed, is equivalent to returning decltype(auto).
    2. SFINAE no longer applies to the expression _VSTD::forward<_T1>(__t) + _VSTD::forward<_T2>(__u). In a bug-free implementation, the operator() declaration would be removed from the overload set, overload resolution will fail, and std::result_of will then do its SFINAE-friendly magic. Instead, the function declaration is successfully instantiated, selected by overload resolution, and then a hard error occurs when the compiler attempts to instantiate the body to actually deduce the return type.

    Your problem is caused by #2.