簡體   English   中英

使用可變參數 arguments 的構造函數重載

[英]Constructor overloading with variadic arguments

首先,我的代碼:

#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);
}

我正在嘗試圍繞std::thread構建一個包裝器(插入冗長的原因列表)。 考慮MyClass在我的實際庫中命名為ThreadWrapper

我希望能夠構建一個 MyClass 作為std::thread的直接替代品。 這意味着能夠做到這一點:

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

但我也想有選擇地給線程一個名字,像這樣:

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

所以我創建了兩個模板構造函數。 如果我只想做一個或另一個並且只使用一個模板構造函數,那么我正在做的很好。

使用編寫的代碼,如果您編譯 (g++),您會得到嚴重的錯誤。 如果我注釋掉更具體的構造函數,我會得到一組不同的討厭的錯誤。 如果我注釋掉不太具體的構造函數(沒有const std::string & arg 的構造函數),那么我嘗試做的一切都有效。 也就是說,帶有std::string的那個是正確的,它可以工作。

發生的事情是,如果我有兩個構造函數,編譯器每次都會選擇不太具體的一個。 我想強迫它使用更具體的。 我想我可以在具有特征的 C++ 17 中做到這一點,但我從未使用過它們,而且我不知道從哪里開始。

現在,我將只使用更具體的版本(帶有名稱的版本)並繼續前進。 但是當我不關心線程名稱時,我想放回不太具體的那個並使用它。

但是有什么方法可以讓我同時擁有兩個模板並讓編譯器根據第一個參數是std::string還是可以變成一個來確定哪個?

沒有人應該在這方面花費大量時間,但如果你看到這個並說,“哦,喬只需要……”那么我很樂意提供幫助。 否則我會忍受這不是 100% 的直接替代品,這很好。

您的代碼有兩個問題:

  1. 當您將模板參數作為&&傳遞給 function 模板時,它們被解釋為“轉發引用”,即它們匹配所有內容,無論它是左值還是右值,無論是否為 const。 更重要的是,它們比某些提供的模板專業化更匹配,這是一個常見的陷阱。 在您的具體情況下,您將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...); }
  2. 為了使其他構造函數調用工作,您需要在withArgs function 中將字符串作為const std::string&而不是std::string&

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

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

但是有什么方法可以讓我同時擁有兩個模板並讓編譯器根據第一個參數是std::string還是可以變成一個來確定哪個?

您可以通過將 SFINAE 用於更通用的構造函數來做到這一點,例如

template< class Function, class... Args, 
          std::enable_if_t<!std::is_convertible_v<Function, std::string>, bool> = true>
explicit MyClass( Function&& f, Args&&... args ) {
   runner(f, args...);
}

如果std::is_convertible_v<Function, std::string>為真,則模板將被丟棄且不考慮重載決議。


不知道為什么,但我也不得不改變

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

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

在進行更改后使其編譯。

如果 function 可與 arguments 一起調用,我將使構造函數可行:

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...);
    }
};

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...);
    }
};

然而

現在,如果您在示例中輸入此代碼,它將無法編譯,因為您的代碼中還有另一個問題:

您傳遞了一個右值字符串,但您的withArgs采用了一個左值引用,因此它不滿足這個概念。 您的代碼沒有這個概念就可以工作,因為您沒有將 arguments 轉發給runner ,因此 runner 不會收到右值引用。 這是您需要解決的問題。 將 arguments 轉發給runner ,然后通過const & bindwithArgs

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM