Search code examples
stringlambdac++17movevariadic

Converting all variadic arguments into a single std::string via move semantics


I have a class that contains a lambda function that looks like this:

class Foo {
private:
    inline static std::string text{};

public:
    template<typename...T>
    inline static auto func = [](T&&...args) mutable throw() {
        text += std::string(args...);
    };

    void show() {
        std::cout << text << '\n';
    }
};

My intended use would be something like this:

int main() {
    Foo bar;
    bar.func<std::string, int, std::string>( "Hello, I am", 39, "years old!");
    bar.show();
    return 0;
}

I want the templated variadic lambda to take in any type of parameter that is a basic type such as string, char*, char[], int, float, double, etc... and to convert all of them into a single std::string that will be stored within the class.

When I run my code as such:

int main() {
    Foo bar;
    bar.func<string>( "Hello world!");
    bar.show();
    return 0;
}

Everything compiles fine, however, when I begin to add in various types such as the example from the intended use above, it fails to compile. Microsoft Visual Studio is giving me a C2400 compiler error: cannot convert from initialize list to std::string. No constructor could take the source type, or constructor overload resolution was ambiguous...

I believe I understand why it is ambiguous as that's not so much the issue. My question is what would be the proper and efficient way of using "move semantics or perfect forwarding"? I'm trying to avoid a bunch of copies of temporaries.


Solution

  • You could use fold expressions:

    template<typename...T>
    inline static auto func = [](T&&...args) mutable throw() {
        text += (toString(args) + ...);
    };
    

    where toString is defined as:

    template<class T>
    std::string toString(T&& t){
        if constexpr (std::is_arithmetic_v<std::decay_t<T>>)
            return std::to_string(std::forward<T>(t));
        else
            return std::forward<T>(t);
    }
    

    you can extend toString to handle all types you need to convert to string. Demo