8

I intend to use shared_ptr quite a bit in an upcoming project, so (not being aware of std::make_shared) I wanted to write a variadic template function spnew<T>(...) as a shared_ptr-returning stand-in for new. Everything went smoothly till I attempted to make use of a type whose constructor includes an initializer_list. I get the following from GCC 4.5.2 when I try to compile the minimal example below:

In function 'int main(int, char**)':
too many arguments to function 'std::shared_ptr spnew(Args ...) [with T = Example, Args = {}]'

In function 'std::shared_ptr spnew(Args ...) [with T = Example, Args = {}]':
no matching function for call to 'Example::Example()'

Oddly enough, I get equivalent errors if I substitute std::make_shared for spnew. In either case, it seems to be incorrectly deducing the parameters when an initializer_list is involved, erroneously treating Args... as empty. Here's the example:

#include <memory>
#include <string>
#include <vector>

struct Example {

    // This constructor plays nice.
    Example(const char* t, const char* c) :
        title(t), contents(1, c) {}

    // This one does not.
    Example(const char* t, std::initializer_list<const char*> c) :
        title(t), contents(c.begin(), c.end()) {}

    std::string title;
    std::vector<std::string> contents;

};

// This ought to be trivial.
template<class T, class... Args>
std::shared_ptr<T> spnew(Args... args) {
    return std::shared_ptr<T>(new T(args...));
}

// And here are the test cases, which don't interfere with one another.
int main(int argc, char** argv) {
    auto succeeds = spnew<Example>("foo", "bar");
    auto fails = spnew<Example>("foo", {"bar"});
}

Is this just an oversight on my part, or a bug?

Jon Purdy
  • 53,300
  • 8
  • 96
  • 166
  • 2
    There's `std::make_shared`, by the way. – GManNickG Apr 27 '11 at 02:18
  • @GMan: Yeah, I've found that and will be using it, but I am still curious what's up with what I wrote. – Jon Purdy Apr 27 '11 at 02:20
  • @GMan: Actually, come to try substituting `make_shared` for `spnew` in my example, it still fails for the `fails` case with equivalent errors. So now at least I know where the error isn't... – Jon Purdy Apr 27 '11 at 02:22
  • 1
    It works on GCC 4.5.1: http://ideone.com/obWSi, with a warning which is valid. Same results on my local 4.6 installation. – Potatoswatter Apr 27 '11 at 02:30
  • @Potatoswatter: Guess I'll just upgrade or avoid, then, and if someone happens to show up with more information, even better. – Jon Purdy Apr 27 '11 at 02:50

2 Answers2

1

You could do this -

#include <memory>
#include <string>
#include <iostream>
#include <vector>

struct Example {

    template<class... Args>
    Example(const char* t, Args... tail) : title(t) 
    {
        Build(tail...);
    }

    template<class T, class... Args>
    void Build(T head, Args... tail) 
    { 
        contents.push_back(std::string(head)); 
        Build(tail...);
    }

    template<class T>
    void Build(T head)
    { 
        contents.push_back(std::string(head)); 
    }

    void Build() {}        

    std::string title;
    std::vector<std::string> contents;

};

template<class T, class... Args>
std::shared_ptr<T> spnew(Args... args) {
    return std::shared_ptr<T>(new T(args...));
}

int main(int argc, char** argv) {
    auto succeeds = spnew<Example>("foo", "bar");
    auto fails = spnew<Example>("foo", "bar", "poo", "doo");

    std::cout << "succeeds->contents contains..." << std::endl;
    for ( auto s : succeeds->contents ) std::cout << s << std::endl;

    std::cout << std::endl << "fails->contents contains..." << std::endl;
    for ( auto s : fails->contents ) std::cout << s << std::endl;
}

This, despite the generic templates is type safe as the compiler will complain about the contents.push_back if the passed type is not convertible to a const char *.

As described above, your code was working fine with gcc 4.6 however the warning you get is explained here why-doesnt-my-template-accept-an-initializer-list, and is possibly not standards compliant, although the c++0x standard is yet to be published so this could change.

Community
  • 1
  • 1
alegalle
  • 19
  • 2
  • This leaks `std::forward(tail)...` and similar in every function with parameter pack. – kyb Mar 30 '20 at 18:35
0

With gcc-4.7 (probably would work on gcc-4.6 too, just branched) with warnings:

foo.cpp: In function ‘int main(int, char**)’:
foo.cpp:29:47: warning: deducing ‘Args ...’ as ‘std::initializer_list<const 
char*>’ [enabled by default]
foo.cpp:22:20: warning:   in call to ‘std::shared_ptr<_Tp1> spnew(Args ...) 
[with T = Example, Args = {const char*, std::initializer_list<const 
char*>}]’ [enabled by default]
foo.cpp:29:47: warning:   (you can disable this with -fno-deduce-init-list) 
[enabled by default]

I'm not sure why anyone would want to beef about init-list deduction though.

There is a related thread: Why doesn't my template accept an initializer list

Basically, a bare init-list doesn't have a type.

Community
  • 1
  • 1
emsr
  • 15,539
  • 6
  • 49
  • 62
  • 1
    As for beef, the default init-list deduction behaviour is an older speculative extension that may conflict with a more recent proposal, which is why `-fno-deduce-init-list` came to be. – Jon Purdy Apr 27 '11 at 04:08