Расширение пакета параметров, содержащего initializer_list, до конструктора

Я намереваюсь использовать shared_ptr совсем немного в следующем проекте, поэтому (не зная о std::make_shared) я хотел написать вариативную функцию шаблона spnew<T>(...) как shared_ptr-возвращающую замену для new. Все шло гладко, пока я не попытался использовать тип, конструктор которого включает initializer_list. Я получаю следующее из GCC 4.5.2, когда пытаюсь скомпилировать минимальный пример ниже:

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()'

Как ни странно, я получаю эквивалентные ошибки, если заменяю std::make_shared на spnew. В любом случае, кажется, что он неправильно выводит параметры, когда задействован initializer_list, ошибочно рассматривая Args... как пустой. Вот пример:

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

Это просто недосмотр с моей стороны или ошибка?


person Jon Purdy    schedule 27.04.2011    source источник
comment
Кстати, там std::make_shared.   -  person GManNickG    schedule 27.04.2011
comment
@GMan: Да, я нашел это и буду использовать, но мне все еще любопытно, что случилось с тем, что я написал.   -  person Jon Purdy    schedule 27.04.2011
comment
@GMan: На самом деле, попробуй заменить make_shared на spnew в моем примере, он все равно терпит неудачу для случая fails с эквивалентными ошибками. Так что теперь, по крайней мере, я знаю, где не ошибка ...   -  person Jon Purdy    schedule 27.04.2011
comment
Он работает в GCC 4.5.1: ideone.com/obWSi с действительным предупреждением. Те же результаты на моей локальной установке 4.6.   -  person Potatoswatter    schedule 27.04.2011
comment
@Potatoswatter: Думаю, я просто обновлюсь или откажусь от него, а если кто-то появится с дополнительной информацией, даже лучше.   -  person Jon Purdy    schedule 27.04.2011


Ответы (2)


Вы могли бы сделать это -

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

Несмотря на то, что общие шаблоны являются типобезопасными, компилятор будет жаловаться на contents.push_back, если переданный тип не может быть преобразован в const char *.

Как описано выше, ваш код отлично работал с gcc 4.6, однако предупреждение, которое вы получаете, объясняется здесь why-doesnt-my-template-accept-an-initializer-list и, возможно, не соответствует стандартам, хотя стандарт c ++ 0x еще не опубликован, поэтому это может измениться.

person alegalle    schedule 05.07.2011
comment
Это утекает std::forward<Args...>(tail)... и аналогично в каждой функции с пакетом параметров. - person kyb; 30.03.2020

С gcc-4.7 (вероятно, будет работать и на gcc-4.6, только что разветвленный) с предупреждениями:

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]

Я не уверен, почему кто-то хочет возражать против вывода списка инициализации.

Существует связанная тема: Почему мой шаблон не принимает список инициализаторов

По сути, простой список инициализации не имеет типа.

person emsr    schedule 27.04.2011
comment
Что касается говядины, поведение вывода списка инициализации по умолчанию - это более старое спекулятивное расширение, которое может конфликтовать с более поздним предложением, поэтому -fno-deduce-init-list и появился. - person Jon Purdy; 27.04.2011