2

Assume the following code, which is a tiny sprintf substitute. (The _itoa and the likes was just used to keep the code short.)

#include <cstdlib>
#include <string>
class Arg {
public:
    Arg(const std::string& s) :m_str(s) {}
    Arg(const char* s) : m_str(s) {}
    Arg(int digi, double number)  {char buf[128]; m_str = _gcvt(number, digi, buf);}
    operator const std::string& ()const { return m_str; }
private:
    std::string m_str;
};

class Format {
public:
    Format(/*const char* format, */std::initializer_list<Arg> args); // see below
    const std::string& str()const { return m_str; }
private:
    std::string m_str;
};

Format::Format(/*const char* format, */std::initializer_list<Arg> args) {
    auto arg = args.begin();
    auto format = std::string(*arg++);
    for(const char* c = format.c_str(); *c!='\0'; ++c) {
        if(*c=='%') { m_str+=*arg++; }
        else { m_str+=*c; }
    }
}


int main() {

    std::string test1 = Format{"test Double:% String:%", {5, 456.78}, "foo"}.str();

    // I want to make this work. See the braces.
    std::string test2 = Format("test Double:% String:%", {5, 456.78}, "foo").str();

    return 0;
}

You see, I want to pass arguments, limited to type "Arg", but use a constructor that uses e.g. varadic templates instead of the initializer_list<> for better readability.

I tried:

    template<typename... T>
    Format(T&& ... args) : Format(std::forward<Args>(args)...) {}

But i get:

error C2440: '<function-style-cast>': cannot convert from 'initializer list' to 'Format'
note: No constructor could take the source type, or constructor overload resolution was ambiguous

3 Answers 3

1

std::initializer_list requires {} not ().

{5, 456.78} has no type and couldn't be deduced for template.

The way to keep your syntax in the old overload way:

Format(Arg arg0) : Format(std::initializer_list{arg0});
Format(Arg arg0, Arg arg1) : Format({arg0, arg1});
Format(Arg arg0, Arg arg1, Arg arg2) : Format({arg0, arg1, arg2});
// ... Up to some limit
Sign up to request clarification or add additional context in comments.

1 Comment

While this seems like a hack, it does exaclty what I want it to do. Thank you.
1

Firstly, you should use braces in member initializer list, to forward to the constructor taking std::initializer_list.

template<typename... T>
Format(T&& ... args) : Format{std::forward<T>(args)...} {}
//                           ^                        ^

Secondly, given Format("test Double:% String:%", {5, 456.78}, "foo"), unfortunately braced-init-list like {5, 456.78} can't be deduced in template type deduction, it has no type. You can specify the type explicitly like

std::string test2 = Format("test Double:% String:%", Arg(5, 456.78), "foo").str();
//                                                      ^         ^

1 Comment

Thank you for the excellent explaination of "why does this fail" (template deduction).
0

You can do like this:

template<typename... T>
Format(T&& ... args) : Format({std::forward<T>(args)...}) {}}

But in call you should manually point that second argument is Arg, like this:

std::string test2 = Format("test Double:% String:%", Arg{5, 456.78}, "foo").str();

example

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.