Как заставить шаблон выводить тип возвращаемого значения функции с переменными аргументами

Кто-то при переполнении стека написал интересный способ захвата лямбды или функтора в ваш собственный класс. Я пытался упростить это, и я думаю, что приблизился, но у меня были некоторые проблемы. Их пример был:

// OT => Object Type
// RT => Return Type
// A ... => Arguments

template<typename OT, typename RT, typename ... A>
struct lambda_expression {
    OT _object;
    RT(OT::*_function)(A...)const; // A pointer to a member function, 
                                   // specifically the operator()

    lambda_expression(const OT & object) // Constructor
        : _object(object), 
          _function(&decltype(_object)::operator()) {} // Assigning the function pointer

    RT operator() (A ... args) const {
        return (_object.*_function)(args...);
    }
};

В основном это позволяет вам:

int captureMe = 2;
auto lambda = [=](int a, int b) { return a + b + captureMe;};
lambda_expression<decltype(lambda), int, int, int>(lambda); 

Я пытался упростить это и подумал, что указатель, содержащийся в классе lambda_expression, не понадобится, потому что вы можете вызвать сам объект функции вместо вызова указателя на operator (). Итак, я попробовал это:

template <typename OT, typename ... Args>   // No Return type specified
struct lambdaContainer
{
    lambdaContainer(OT funcObj) : funcObj(funcObj){ }
    OT funcObj; // No pointer, just the function object.

    auto operator()(Args... args) 
    {
        return funcObj(args...); // Call the function object directly
    }
};

Тогда что-то вроде:

int captureMe = 2;
auto lambda = [=](int a, int b) { return a + b + captureMe; };

lambdaContainer<decltype(lambda), int, int> lam(lambda);

auto i = lam(1, 1);
// i = 4;

Где я написал строчку:

auto operator()(Args... args) 
    {
        return funcObj(args...); 
    }

Видимо:

 decltype(auto) operator()(Args... args) //works in C++14 apparently.

Но я попробовал без ключевого слова auto, и у меня это с треском провалилось, я хочу понять, как работает Args ... Я пытался:

decltype(funObj(Args...) operator()(Args... args) // this failed
decltype(OT(Args...) operator() (Args... args) // this failed
auto operator() (Args... args) -> decltype(funcObj(Args...)) // this failed
auto operator() (Args... args) -> decltype(OT(Args...)) // this failed

Как я могу расширить параметр Args, чтобы шаблон мог определить тип возвращаемого значения? Это возможно только с авто?


person Zebrafish    schedule 25.09.2017    source источник
comment
Это может сработать неправильно, но я предлагаю вам прочитать хорошую книгу по C ++, в частности, продвинутые. Изучать C ++ по таким отрывочным фрагментам невероятно сложно.   -  person Passer By    schedule 25.09.2017


Ответы (1)


decltype(e) принимает выражение e и оценивает тип этого выражения. Вам необходимо предоставить выражение, которое представляет вызов сохраненной лямбды:

auto operator()(Args... args) 
    -> decltype(std::declval<OT>()(std::declval<Args>()...))

В этом случае я использую std::declval для создания «поддельного экземпляра» объектов, которые могут использоваться для целей дедукции, без фактического вызова какого-либо конструктора.

Давайте разберемся еще дальше:

-> decltype(
    std::declval<OT>()          // create a fake instance of `OT`
    (                           // invoke it
        std::declval<Args>()... // create a fake instance of each argument 
                                // type, expanding `Args...`
    )
)

живой пример на wandbox


Между прочим, вы все равно должны std::forward использовать аргументы в вызове funcObj, поскольку могут быть некоторые ссылки на rvalue, которые необходимо распространить ниже:

auto operator()(Args... args) 
{
    return funcObj(std::forward<Args>(args)...); 
}
person Vittorio Romeo    schedule 25.09.2017
comment
Это последнее, о чем я мог подумать. Я читал про declval и std :: forward. declval Я вроде как понимаю, но два других не понимаю. Интересно, что вы говорите, что declval создает «фальшивую» версию объекта. - person Zebrafish; 25.09.2017
comment
@Zebrafish: все, что делает declval, это возвращает ссылочный тип. Поскольку std::declval<T>() является заменой T{} в неоцененных контекстах, мне нравится думать, что это создает поддельный экземпляр. Но он просто возвращает ссылку :) - person Vittorio Romeo; 25.09.2017
comment
@Zebrafish: какие вещи я должен уточнить дальше? - person Vittorio Romeo; 25.09.2017