Я пытаюсь предоставить оболочку вокруг std::invoke
, чтобы выполнять работу по выводу типа функции, даже когда функция перегружена.
(я спросил связанный вопрос вчера для версии с переменным числом аргументов и указателем метода).
Когда функция имеет один аргумент, этот код (C++17) работает, как и ожидалось, при нормальных условиях перегрузки:
#include <functional>
template <typename ReturnType, typename ... Args>
using FunctionType = ReturnType (*)(Args...);
template <typename S, typename T>
auto Invoke (FunctionType<S, T> func, T arg)
{
return std::invoke(func, arg);
}
template <typename S, typename T>
auto Invoke (FunctionType<S, T&> func, T & arg)
{
return std::invoke(func, arg);
}
template <typename S, typename T>
auto Invoke (FunctionType<S, const T&> func, const T & arg)
{
return std::invoke(func, arg);
}
template <typename S, typename T>
auto Invoke (FunctionType<S, T&&> func, T && arg)
{
return std::invoke(func, std::move(arg));
}
Уменьшение раздувания кода, очевидно, необходимо для большего количества входных аргументов, но это отдельная проблема.
Если у пользователя есть перегрузки, отличающиеся только const/references, например:
#include <iostream>
void Foo (int &)
{
std::cout << "(int &)" << std::endl;
}
void Foo (const int &)
{
std::cout << "(const int &)" << std::endl;
}
void Foo (int &&)
{
std::cout << "(int &&)" << std::endl;
}
int main()
{
int num;
Foo(num);
Invoke(&Foo, num);
std::cout << std::endl;
Foo(0);
Invoke(&Foo, 0);
}
Затем Invoke
неправильно выводит функцию с выводом g++:
(целое &)
(константное целое &)(целое &&)
(константное целое &)
И клан++:
(целое &)
(константное целое &)(целое &&)
(целое &&)
(Спасибо geza за указание на то, что выходы clang были другими).
Итак, Invoke
имеет неопределенное поведение.
Я подозреваю, что метапрограммирование было бы подходом к решению этой проблемы. Независимо от этого, возможно ли правильно обрабатывать вывод типа на сайте Invoke
?
(int &&)
дважды для второго случая. - person geza   schedule 08.03.2020S
аргумента. Попробуйте закомментироватьconst T &
версиюInvoke
и обратите внимание на ошибку. Также, если аргумент указан явно (Invoke<void>(&Foo, num)
), вызывается правильная версия. - person aparpara   schedule 08.03.2020const T&
, то это просто уход от проблемы без ее решения. - person Elliott   schedule 08.03.2020Invoke
, он может создать его как с константным, так и с неконстантнымFoo
. И он не проверяет, что возвращаемый тип (S
) одинаков для обоих, поэтому он говорит, что не может вывестиS
. Поэтому он игнорирует этот шаблон. В то время как создание экземпляра constInvoke
может быть выполнено только с constFoo
, поэтому в этом случае он может вывестиS
. Следовательно, компилятор использует этот шаблон. - person geza   schedule 08.03.2020&&
аргумента функцииFoo()
является ссылкой совершенно другого типа ( ссылка на значение r) по сравнению с&&
дляarg
изInvoke
(это ссылка на значение шаблона, а также ссылка на пересылку). - person max66   schedule 08.03.2020Invoke
не принимает это во внимание? - person Elliott   schedule 08.03.2020&&
версииInvoke()
)std::move()
вместоstd::forward
; во-вторых, потому что мне кажется (возможно, я ошибаюсь), что ваш код основан на идее, чтоT && arg
всегда естьT &&
(в шаблонной функции). - person max66   schedule 08.03.2020std::move
, мое понимание было (правильным или нет), что любая входная функцияBar(T&&)
будет выведена какBar (T&)
, если вход был lvalue . Если вы замените main следующим кодом, он будет вести себя так, как я ожидал в g++, но не будет компилироваться в clang++:template <typename T> void Bar (T && t) { Foo(std::forward<T>(t)); } int main() { int num; Bar(num); Invoke<void>(&Bar, num); }
- person Elliott   schedule 09.03.2020Bar(T&&)
будет выведена какBar (T&)
, если бы ввод был lvalue. Поэтому, если вы используетеstd::move()
вместо l-значения, вы (потенциально) совершите катастрофу: внутри функции вы можете сделать функцию устаревшей, когда ожидается, что извне она останется действительной. - person max66   schedule 09.03.2020Bar(T&&)
выводится вBar(int&)
, скажем, из-за вызова int lvalue, я думал, что когда вы делаетеint x; Invoke(&Bar, x)
, он вызоветInvoke (FunctionType<S, T&>, T&)
. Это то, что происходит с g++ (используя мой код из моего предыдущего комментария и помещая печать в вызовы), но, к сожалению, это вызывает ошибку компиляции для clang++. - person Elliott   schedule 09.03.2020