Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Constructor overloading with variadic arguments

First, my code:

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

I'm trying to build a wrapper around std::thread for (insert lengthy list of reasons). Consider MyClass here to be named ThreadWrapper in my actual library.

I want to be able to construct a MyClass as a direct replacement for std::thread. This means being able to do this:

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

But I also want to optionally give threads a name, something like this:

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

So I created two template constructors. If I only want to do one or the other and only use a single template constructor, what I'm doing is fine.

With the code as written, if you compile (g++), you get nasty errors. If I comment out the more specific constructor, I get a different set of nasty errors. If I comment out the less specific constructor (the one that doesn't have a const std::string & arg), then everything I'm trying to do works. That is, the one with std::string is the right one, and it works.

What's happening is that if I have both constructors, the compiler picks the less specific one each time. I want to force it to use the more specific one. I think I can do this in C++ 17 with traits, but I've never used them, and I wouldn't know where to begin.

For now, I'm going to just use the more specific version (the one that takes a name) and move on. But I'd like to put the less specific one back in and use it when I don't care about the thread names.

But is there some way I can have both templates and have the compiler figure out which one based on whether the first argument is either a std::string or can be turned into one?

No one should spend significant time on this, but if you look at this and say, "Oh, Joe just has to..." then I'd love help. Otherwise I'll just live with this not being 100% a direct drop-in replacement, and that's fine.

like image 375
Joseph Larson Avatar asked Dec 13 '21 21:12

Joseph Larson


People also ask

What is constructor overloading explain with example?

Example 1: Constructor overloadingWhen the object person1 is created, the first constructor is called because we have not passed any argument. This constructor initializes age to 20 . When person2 is created, the second constructor is called since we have passed 45 as an argument.

Can you overload constructor and destructor justify with suitable program?

Answer: No, we cannot overload a destructor of a class in C++ programming.

Can constructor be overloaded in C++?

In C++, We can have more than one constructor in a class with same name, as long as each has a different list of arguments. This concept is known as Constructor Overloading and is quite similar to function overloading.

Can we overload constructor in derived class?

Can constructors be overloaded in derived class? Explanation: The constructor must be having the same name as that of a class. Hence a constructor of one class can't even be defined in another class. Since the constructors can't be defined in derived class, it can't be overloaded too, in derived class.


1 Answers

Your code has two problems:

  1. When you pass template parameters as && into a function template, they are interpreted as "forwarding references", i.e. they match everything regardless whether it is an lvalue or rvalue, const or not. And more importantly, it's a common pitfall that they are a better match than some provided template specialization. In your concrete case, you pass string{"hasArgs"} as rvalue, but the specialized constructor expects a const lvalue ref, so it is discarded. To fix this, you can, as you suggested, use type traits to disable the forwarding constructor in this specific case:

    // 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. In order to make the other constructor call work, you need to take the string as const std::string& not std::string& in the withArgs function

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

Full working example here: https://godbolt.org/z/oxEjoEeqn

like image 64
florestan Avatar answered Sep 25 '22 10:09

florestan