使用可变参数重载构造函数

Jos*_*son 5 c++ variadic-templates

首先,我的代码:

#include <iostream>
#include <functional>
#include <string>
#include <thread>
#include <chrono>

using std::string;
using namespace std::chrono_literals;

class MyClass {
public:
    MyClass() {}

    // More specific constructor.
    template< class Function, class... Args >
    explicit MyClass( const std::string & theName, Function&& f, Args&&... args )
        : name(theName)
    {
        runner(f, args...);
    }

    // Less specific constructor
    template< class Function, class... Args >
    explicit MyClass( Function&& f, Args&&... args ) {
        runner(f, args...);
    }

    void noArgs() { std::cout << "noArgs()...\n"; }
    void withArgs(std::string &) { std::cout << "withArgs()...\n"; }

    template< class Function, class... Args >
    void runner( Function&& f, Args&&... args ) {
        auto myFunct = std::bind(f, args...);

        std::thread myThread(myFunct);
        myThread.detach();
    }

    std::string name;
};

int main(int, char **) {
    MyClass foo;

    foo.runner (&MyClass::noArgs, &foo);
    foo.runner (&MyClass::withArgs, &foo, std::string{"This is a test"} );

    MyClass hasArgs(string{"hasArgs"}, &MyClass::withArgs, foo, std::string{"This is a test"} );

    std::this_thread::sleep_for(200ms);
}
Run Code Online (Sandbox Code Playgroud)

我正在尝试构建一个包装器std::thread(插入冗长的原因列表)。考虑在这里在我的实际图书馆中MyClass命名。ThreadWrapper

我希望能够构建一个 MyClass 作为std::thread. 这意味着能够做到这一点:

MyClass hasArgs(&MyClass::withArgs, foo, std::string{"This is a test"} );
Run Code Online (Sandbox Code Playgroud)

但我也想选择给线程一个名称,如下所示:

MyClass hasArgs(string{"hasArgs"}, &MyClass::withArgs, foo, std::string{"This is a test"} );
Run Code Online (Sandbox Code Playgroud)

所以我创建了两个模板构造函数。如果我只想执行其中之一并且只使用单个模板构造函数,那么我所做的就很好。

使用编写的代码,如果您编译(g++),您会遇到严重的错误。如果我注释掉更具体的构造函数,我会得到一组不同的令人讨厌的错误。如果我注释掉不太具体的构造函数(没有参数的构造函数const std::string &),那么我尝试做的所有事情都会起作用。也就是说,带有的std::string就是正确的,并且有效。

发生的情况是,如果我有两个构造函数,编译器每次都会选择不太具体的一个。我想强制它使用更具体的一个。我认为我可以在 C++ 17 中使用特征来做到这一点,但我从未使用过它们,而且我不知道从哪里开始。

现在,我将只使用更具体的版本(带有名称的版本)并继续。但我想把不太具体的一个放回去,并在我不关心线程名称时使用它。

但是有什么方法可以让我同时拥有两个模板并让编译器根据第一个参数是否是 astd::string或可以变成 a 来确定哪个模板吗?

没有人应该在这方面花费大量时间,但如果您看到这一点并说,“哦,乔只需要......”那么我很乐意提供帮助。否则我会接受这不是 100% 的直接替代品,这很好。

flo*_*tan 5

您的代码有两个问题:

  1. 当您将模板参数 as 传递&&到函数模板时,它们被解释为“转发引用”,即它们匹配所有内容,无论它是左值还是右值,无论是否为常量。更重要的是,一个常见的陷阱是它们比某些提供的模板专业化更好匹配。在您的具体情况中,您string{"hasArgs"}作为右值传递,但专用构造函数需要 const 左值引用,因此它被丢弃。要解决此问题,您可以按照您的建议,在这种特定情况下使用类型特征来禁用转发构造函数:

    // Less specific constructor
    template< class Function, class... Args, std::enable_if_t<std::is_invocable_v<Function, Args...>, int> = 0>
    explicit MyClass( Function&& f, Args&&... args ) {
        runner(f, args...);
    }
    
    Run Code Online (Sandbox Code Playgroud)
  2. 为了使其他构造函数调用工作,您需要将字符串视为const std::string&不在函数std::string&withArgs

    void withArgs(const std::string &) { std::cout << "withArgs()...\n"; }
    
    Run Code Online (Sandbox Code Playgroud)

完整的工作示例在这里: https: //godbolt.org/z/oxEjoEeqn


bol*_*lov 2

我将使构造函数可行,当且仅当函数可以使用参数调用:

C++20 概念

class MyClass {
public:
    MyClass() {}

    // More specific constructor.
    template< class Function, class... Args >
        requires std::invocable<Function, Args...>
    explicit MyClass( const std::string & theName, Function&& f, Args&&... args )
        : name(theName)
    {
        runner(f, args...);
    }

    // Less specific constructor
    template< class Function, class... Args >
        requires std::invocable<Function, Args...>
    explicit MyClass( Function&& f, Args&&... args ) {
        runner(f, args...);
    }
};
Run Code Online (Sandbox Code Playgroud)

C++17

class MyClass {
public:
    MyClass() {}

    // More specific constructor.
    template< class Function, class... Args,
              std::enable_if_t<std::is_invocable_v<Function, Args...>, std::nullptr_t> = nullptr>
    explicit MyClass( const std::string & theName, Function&& f, Args&&... args )
        : name(theName)
    {
        runner(f, args...);
    }

    // Less specific constructor
    template< class Function, class... Args,
              std::enable_if_t<std::is_invocable_v<Function, Args...>, std::nullptr_t> = nullptr>
    explicit MyClass( Function&& f, Args&&... args ) {
        runner(f, args...);
    }
};
Run Code Online (Sandbox Code Playgroud)

然而

现在,如果您在示例中放入此代码,它将无法编译,因为您的代码中存在另一个问题:

您传递了一个右值字符串,但您withArgs采用了一个左值引用,因此它不满足这个概念。您的代码可以在没有这个概念的情况下工作,因为您没有将参数转发到,runner因此运行程序不会收到右值引用。这是你需要解决的问题。将参数转发到runner,然后转发到bindwithArgsto take by const &