Возможны ли общие оболочки функций в C++?

Это появилось, когда я искал ошибку в оболочке функции boost::fusion::fused при использовании decltype. Похоже, проблема заключается в том, что недопустимый decltype является ошибкой компиляции, даже если экземпляр шаблона, для которого он требуется, не будет использоваться, и я не могу понять, как обойти это, чтобы создать универсальную оболочку функции.

Вот моя попытка создания оболочки с одним аргументом:

#include <utility>
#include <type_traits>

template <class T>
typename std::add_rvalue_reference<T>::type declval();

template <class Fn, class Arg>
struct get_return_type
{
    typedef decltype(declval<Fn>()(declval<Arg>())) type;
};

template <class Fn>
struct wrapper
{
    explicit wrapper(Fn fn) : fn(fn) {}
    Fn fn;

    template <class Arg>
    typename get_return_type<Fn,Arg&&>::type
        operator()(Arg&& arg)
    {
        return fn(std::forward<Arg>(arg));
    }

    template <class Arg>
    typename get_return_type<const Fn,Arg&&>::type
        operator()(Arg&& arg)
    {
        return fn(std::forward<Arg>(arg));
    }
};

Проблема в том, что это не работает в случаях, когда аргументы неконстантной версии нельзя преобразовать в аргументы константной версии. Например:

#include <iostream>

struct x {};
struct y {};

struct foo
{
    void operator()(x) { std::cout << "void operator()(x)" << std::endl; }
    void operator()(y) const { std::cout << "void operator()(y) const" << std::endl; }
};

int main()
{
    wrapper<foo> b = wrapper<foo>(foo());
    b(x()); // fail
}

Мне кажется, что сбой выражения decltype, вызванный void operator()(y) const, должен просто привести к удалению этой функции из-за SFINAE.


person Ayjay    schedule 18.05.2012    source источник
comment
разве не должно быть возврата в ваших операторах для обертки?   -  person BЈовић    schedule 18.05.2012
comment
@VJovic Упс! Спасибо, я добавил их сейчас.   -  person Ayjay    schedule 18.05.2012
comment
Также второй оператор() в обертке должен быть константным. Какой компилятор? В чем ошибка? Для g++ 4.6.1 я получаю странную ошибку: no match for call to (const foo)(x)   -  person BЈовић    schedule 18.05.2012
comment
Я пробовал это с g++ 4.7 и VS2010. Оба терпят неудачу одинаково - экземпляр шаблона get_return_type<const Fn,Arg&&>::type терпит неудачу и вместо того, чтобы быть удаленным из-за SFINAE, вызывает ошибку компиляции.   -  person Ayjay    schedule 18.05.2012


Ответы (1)


Вот код, который отлично компилируется на g++ 4.6.1:

#include <utility>
#include <type_traits>
#include <iostream>

template <class Fn, class Arg>
struct get_return_type
{
    typedef decltype( std::declval<Fn>() ( std::declval<Arg>() ) ) type;
};

template <class Fn>
struct wrapper
{
    explicit wrapper(Fn fn) : fn(fn) {}
    Fn fn;

    template <class Arg>
    typename get_return_type<Fn,Arg&&>::type
    operator()(Arg&& arg)
    {
        return fn(std::forward<Arg>(arg));
    }

    template <class Arg>
    typename get_return_type< const Fn,Arg&&>::type
    operator()(Arg&& arg) const
    {
        return fn(std::forward<Arg>(arg));
    }
};

struct x {};
struct y {};

struct foo
{
    x* operator()(x) { std::cout << "x* operator()(x)" << std::endl; return nullptr; }
    x operator()(x) const { std::cout << "x operator()(x) const" << std::endl; return x(); }
    y* operator()(y) { std::cout << "y* operator()(y)" << std::endl; return nullptr; }
    y operator()(y) const { std::cout << "y operator()(y) const" << std::endl; return y(); }
};

template <class Fn>
void test_foo(Fn fn)
{
    // make sure all operator() overloads are callable
    const Fn& cfn = fn;

    x* a = fn(x());
    x b = cfn(x());
    y* c = fn(y());
    y d = cfn(y());
(void)a;(void)b;(void)c;(void)d;
}

int main()
{
    test_foo(foo());
    test_foo(wrapper<foo>(foo())); // fail
}
person BЈовић    schedule 18.05.2012
comment
Ну, если вы измените функтор, тогда конечно, но суть в том, можно ли написать оболочку функции, которая может обернуть каждую функцию? - person Ayjay; 18.05.2012
comment
@Ayjay Приведенный выше код является фиксированной версией, которая отлично компилируется как для x, так и для y :) - person BЈовић; 18.05.2012
comment
Но теперь он неправильно определяет возвращаемый тип функтора const. - person Ayjay; 21.05.2012
comment
@Ayjay, что ожидается? что ты получил? - person BЈовић; 21.05.2012
comment
@BЈовић Я хочу, чтобы класс-оболочка мог обернуть любой объект функции, который содержит произвольное количество operator() перегрузок. Пока вызываемый объект вызывает допустимую перегрузку, она должна работать. - person Ayjay; 21.05.2012
comment
@Ayjay Я пробовал разные сигнатуры operator(), и все работали. - person BЈовић; 21.05.2012
comment
@Ayjay Я справился и вставил ваш пример, немного изменил его, и он работает. Найдите исправленную версию в редактировании - person BЈовић; 21.05.2012