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?
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With