Я беру код из решения одного из старых вопросов из здесь

#include <utility>
#include <iostream>
class Test {
  public:
  Test() {
      std::cout << "ctor" << std::endl;
  }
  Test(const Test&) {
      std::cout << "copy ctor" << std::endl;
  }
  Test(Test&&) {
      std::cout << "move ctor" << std::endl;
  }
};

void func(Test const&)
{
    std::cout << "requires lvalue" << std::endl;
}

void func(Test&&)
{
    std::cout << "requires rvalue" << std::endl;
}

template<typename Arg>
void pass(Arg&& arg) {
    // use arg here
    func(std::forward<Arg>(arg));
    return; 
}

template<typename Arg, typename ...Args>
void pass(Arg&& arg, Args&&... args)
{
    // use arg here
    return pass(std::forward<Args>(args)...);
}

int main(int, char**)
{
    pass(std::move<Test>(Test()));
    return 0;
}

Мне интересно, существует ли способ избежать двух перегрузок функции с именем pass над чем-то вроде void pass(Arg&& ...arg) и при этом достичь той же функциональности, что и выше?

Меня смущает понимание того, когда мне нужно перегружать код, как показано в приведенном выше коде.

3
Test 18 Янв 2022 в 13:51
1
Обратите внимание, что этот код довольно старомоден. Он использует рекурсию (которая в настоящее время не нужна), и, как всегда с рекурсией, вам нужно где-то остановиться.
 – 
463035818_is_not_a_number
18 Янв 2022 в 13:57
: именно здесь я запутался и подумал, есть ли возможность переписать сейчас дни, чтобы сделать это проще.
 – 
Test
18 Янв 2022 в 14:01
Это неправильное использование std::move в pass(std::move<Test>(Test()));, std::move выводит его аргумент, поэтому <Test> следует удалить; Test() уже является r-значением, поэтому std::move не нужен.
 – 
Jarod42
18 Янв 2022 в 14:04

3 ответа

Вы можете использовать if constexpr:

template<typename Arg, typename ...Args>
void pass(Arg&& arg, Args&&... args)
{
    // use arg here

    if constexpr (sizeof... (Args) == 0) {
        func(std::forward<Arg>(arg));
    }
    else {
        pass(std::forward<Args>(args)...);
    }
}
4
Holt 18 Янв 2022 в 13:54
Почему вы все еще должны писать (Arg&& arg, Args&&... args) в объявлении.. вы не можете просто сказать (Args&& ...args)? Если нет, то почему?
 – 
Test
18 Янв 2022 в 13:58
Если вы применяете одно и то же к каждому элементу args, тогда вам не нужен arg, но это не ваш случай, поскольку вы "используете arg" для каждого элемента args, но затем вызовите func для последнего элемента, и в этом случае вам нужен способ различать pass с одним или несколькими аргументами. Это один из способов использования рекурсии, как в исходном коде. Есть и другие способы, но более запутанные, потому что извлечение последнего элемента вариационного пакета не является тривиальной задачей.
 – 
Holt
18 Янв 2022 в 14:03

В C++17 вы можете использовать выражения fold:

template <typename ... Ts>
void pass(Ts&&... args)
{
    [[maybe_unused]] auto f = [](auto&& arg){ /* use arg here */};
    (f(std::forward<Ts>(args)), ...);
    func((std::forward<Ts>(args), ...)); // call func only on last parameter
                                      // (assuming no evil overload of operator,)
}
4
Jarod42 18 Янв 2022 в 14:09
Вам не хватает последнего вызова глобального func для последнего элемента args.
 – 
Holt
18 Янв 2022 в 14:00
@Holt: Не уверен, что это не опечатка OP, иначе func((std::forward<Ts>(args), ...)); должно помочь. Всё равно добавил.
 – 
Jarod42
18 Янв 2022 в 14:08

Не совсем то, что вы просили, но... обратите внимание, что (также до С++ 17) вместо шаблонной функции рекурсии в первом случае, которая получает arg и дублирует одиночное управление arg

template <typename Arg>
void pass (Arg && arg) {
    func(std::forward<Arg>(arg));
}

Вы можете просто написать не шаблонную пустую (без arg полученную, без arg управляемую) функцию pass()

void pass ()
{ }
1
max66 18 Янв 2022 в 14:15