Неожиданный сбой SFINAE с использованием std :: result_of

В C ++ 14 предполагается, что std :: result_of приведет к SFINAE, если выражение имеет неправильный формат *. Вместо этого я получаю ошибку компиляции («недопустимые операнды для двоичного выражения») в моем последнем случае ниже (т.е. позволяя компилятору вывести тип для std :: plus ‹>). Первые три случая работают должным образом. Код и результаты показаны ниже.

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

Выход:

% 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

Будем признательны за любые указатели на то, что я делаю неправильно!

Спасибо.

  • С сайта cppreference.com. Я думаю, что соответствующая стандартная ссылка - 20.10.7.6, комментарии к последней записи в таблице.

person KentH    schedule 18.02.2015    source источник
comment
Он компилируется в ствол GCC и clang 3.5.   -  person    schedule 18.02.2015
comment
Блин, я сначала неправильно понял вопрос. Похоже, это связано с библиотекой. Я не могу воспроизвести ошибку ни с clang 3.5, ни с gcc 4.9, используя libstdc ++, но я могу воспроизвести ее с libc ++, используя оба компилятора.   -  person Wintermute    schedule 18.02.2015
comment
@Wintermute В clang 3.6 с libc ++ он не компилируется. Но дает разные ошибки. Кроме того, как вы используете GCC с libc ++?   -  person    schedule 18.02.2015


Ответы (1)


Это вызвано ошибкой в ​​libc ++, о которой я сообщил буквально через несколько дней. тому назад. (Обновление: ошибка исправлена ​​ в магистрали.)

Проблема в том, что их реализация «ромбовидного функтора» не соответствует требованиям. Например, они реализовали std::plus<void>::operator() следующим образом:

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

когда это должно быть

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

Отсутствующий завершающий-возвращаемый-тип означает две вещи:

  1. Они больше не «идеально возвращаются»; вместо этого тип возвращаемого значения определяется с использованием правил для auto, что по существу приводит к его распаду. Конечный тип возврата, когда выражение в нем правильно сформировано, эквивалентен возврату decltype(auto).
  2. SFINAE больше не применяется к выражению _VSTD::forward<_T1>(__t) + _VSTD::forward<_T2>(__u). В реализации без ошибок объявление operator() будет удалено из набора перегрузки, разрешение перегрузки завершится ошибкой, и std::result_of затем совершит свою магию, дружественную к SFINAE. Вместо этого объявление функции успешно создается, выбирается разрешением перегрузки, а затем возникает серьезная ошибка, когда компилятор пытается создать экземпляр тела, чтобы фактически определить возвращаемый тип.

Ваша проблема вызвана №2.

person T.C.    schedule 18.02.2015
comment
@TC - спасибо. Я никогда не считал ошибку libc ++, потому что мой обычный источник ошибок и недопонимания - это я! У вас есть предлагаемый способ обхода? Кроме того, я отмечаю, что в моем третьем случае выше использование plus ‹int *› вместо plus ‹int› приводит к той же проблеме, что и случай с ромбовидным функтором. - person KentH; 18.02.2015
comment
@KentH plus<non-void> и т. Д. Не подходят для SFINAE. Подпись operator() не удаляется SFINAE, если тело не компилируется, поэтому вместо этого вы получаете жесткую ошибку, когда фактически пытаетесь ее вызвать. К сожалению, для функторов, не поддерживающих SFINAE, можно сделать немногое. - person T.C.; 18.02.2015