Создание экземпляра шаблона C++ параметров шаблона функции

У меня есть следующая проблема с использованием экземпляра шаблона [*].

файл foo.h

class Foo
{
public:
    template <typename F>
    void func(F f)

private:
    int member_;
};

файл foo.cc

template <typename F>
Foo::func(F f)
{
     f(member_);
}

файл caller.cc

Foo::func(boost::bind(&Bar::bar_func, bar_instance, _1));

Хотя это компилируется нормально, компоновщик жалуется на неопределенный символ:

void Foo::func<boost::_bi::bind_t...>

Как создать экземпляр функции Foo::func? Поскольку он принимает функцию в качестве аргумента, я немного смущен. Я попытался добавить функцию инстанцирования в foo.cc, как я привык к обычным нефункциональным типам:

instantiate()
{
    template<> void Foo::func<boost::function<void(int)> >(boost::function<void(int)>);
}

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

Спасибо!

[*] Да, я читал облегчённый FAQ по parashift.


person mavam    schedule 23.10.2008    source источник
comment
Принудительное создание экземпляра с помощью: template void Foo::func‹myFunc›(myFunc f);   -  person Martin York    schedule 23.10.2008


Ответы (5)


Ответ на этот вопрос зависит от компилятора. Некоторые версии компилятора Sun C++ обрабатывают это автоматически, создавая кэш реализаций шаблонных функций, которые будут совместно использоваться отдельными единицами трансляции.

Если вы используете Visual C++ и любой другой компилятор, который не может этого сделать, вы также можете поместить определение функции в заголовок.

Не беспокойтесь о повторяющихся определениях, если заголовок включен в несколько файлов .cc. Компилятор помечает методы, сгенерированные шаблоном, специальным атрибутом, чтобы компоновщик знал, что нужно отбрасывать дубликаты, а не жаловаться. Это одна из причин, по которой в С++ существует «правило одного определения».

Редактировать. Приведенные выше комментарии применимы в общем случае, когда ваш шаблон должен поддерживать связь с любыми параметрами типа. Если вы знаете закрытый набор типов, которые будут использовать клиенты, вы можете обеспечить их доступность, используя явное создание экземпляров в файле реализации шаблона, что заставит компилятор генерировать определения для других файлов, с которыми они будут связываться. Но в общем случае, когда ваш шаблон должен работать с типами, которые, возможно, известны только клиенту, тогда нет особого смысла разделять шаблон на заголовочный файл и файл реализации; любой клиент должен включать обе части в любом случае. Если вы хотите изолировать клиентов от сложных зависимостей, скройте эти зависимости за функциями без шаблонов, а затем вызовите их из кода шаблона.

person Daniel Earwicker    schedule 23.10.2008
comment
... после редактирования: результатом boost::bind является «неопределенный» тип, поэтому явное создание экземпляра шаблона в этом случае не будет хорошим решением (вы можете прочитать реализацию bind.hpp и определить реальный тип для шаблона создание экземпляра, но затем обновления в библиотеке связывания могут сломать его, поскольку тип не является частью интерфейса). - person David Rodríguez - dribeas; 08.07.2009

Разделение на файлы Как вы хотите:
Не то, чтобы я рекомендовал это. Просто показываю, что это возможно.

шлеп.ч

#include <iostream>
class Foo
{
public:
    Foo(): member_(15){}


    // Note No definition of this in a header file.
    // It is defined in plop.cpp and a single instantiation forced
    // Without actually using it.
    template <typename F>
    void func(F f);

private:
    int member_;
};


struct Bar
{
     void bar_func(int val) { std::cout << val << "\n"; }
};

struct Tar
{
    void tar_func(int val) { std::cout << "This should not print because of specialisation of func\n";}
};

Plop.cpp

#include "plop.h"
#include <boost/bind.hpp>
#include <iostream>

template <typename F>
void Foo::func(F f)
{
     f(member_);
}

// Gnarly typedef
typedef boost::_bi::bind_t<void, boost::_mfi::mf1<void, Bar, int>, boost::_bi::list2<boost::_bi::value<Bar>, boost::arg<1> (*)()> > myFunc;

// Force the compiler to generate an instantiation of Foo::func()
template void Foo::func<myFunc>(myFunc f);

// Note this is not a specialization as that requires the <> after template.
// See main.cpp for an example of specialization.

main.cpp

#include "plop.h"
#include <boost/bind.hpp>
#include <iostream>

// Gnarly typedef
typedef boost::_bi::bind_t<void, boost::_mfi::mf1<void, Tar, int>, boost::_bi::list2<boost::_bi::value<Tar>, boost::arg<1> (*)()> > myTar;

// Specialization of Foo::func()
template<> void Foo::func<myTar>(myTar f)
{
    std::cout << "Special\n";
}
// Note. This is not instantiated unless it is used.
// But because it is used in main() we get a version.

int main(int argc,char* argv[])
{
    Foo f;
    Bar b;
    Tar t;

    f.func(boost::bind(&Bar::bar_func, b, _1)); // Uses instantiation from plop.cpp
    f.func(boost::bind(&Tar::tar_func, t, _1)); // Uses local specialization
}
person Martin York    schedule 23.10.2008
comment
Проблема, которую я вижу в этом решении, заключается в том, что конкретный тип, возвращаемый boost::bind, не является частью их общедоступного интерфейса (документы). В документах (boost::bind) говорится, что это «неизвестный тип», и для меня это означает, что конкретный тип (показан выше) не должен использоваться и что тип может быть изменен в любой момент времени (нарушение код выше). - person David Rodríguez - dribeas; 08.07.2009

Вы включаете foo.cc в caller.cc. Создание экземпляров — это то, что происходит во время компиляции: когда компилятор видит вызов в вызывающей программе, он создает экземпляры версий шаблонов, но ему необходимо иметь полное определение.

person Lou Franco    schedule 23.10.2008
comment
... или когда вы явно создаете его экземпляр. - person David Rodríguez - dribeas; 08.07.2009

Я думаю, что они оба имеют в виду, что определения шаблонных функций (а не только объявления) должны быть включены в файл, в котором они используются. Шаблонные функции на самом деле не существуют до тех пор, пока они не используются; если вы поместите их в отдельный файл cc, то компилятор не будет знать о них в других файлах cc, если только вы явно не #include добавите этот файл cc либо в заголовочный файл, либо в файл, который их вызывает, из-за того, как синтаксический анализатор работает.

(Вот почему определения шаблонных функций обычно хранятся в файлах заголовков, как описал Эрвикер.)

Есть яснее?

person Head Geek    schedule 23.10.2008
comment
На самом деле вы можете принудительно создать экземпляр, не используя его. См. ниже, где я разбиваю код на три файла и создаю экземпляр без его использования. - person Martin York; 23.10.2008
comment
Распространенной ошибкой является предположение, что, поскольку в большинстве случаев шаблоны определяются в заголовочном файле, так и должно быть. - person David Rodríguez - dribeas; 08.07.2009
comment
Если вы планируете использовать их более чем в одном файле .cpp, вы должны определить их в заголовочном файле. Я уверен, что есть несколько вымученных исключений из этого правила, но это правило по уважительной причине. - person Head Geek; 08.07.2009

Я считаю, что Эрвикер прав. Проблема с явным созданием экземпляра функции-члена шаблона func в этом случае заключается в том, что тип, возвращаемый boost::bind, зависит от реализации. Это не функция boost::function. Boost::function может содержать boost:bind, потому что у него есть оператор присваивания шаблона, который выводит тип правой части (результат boost::bind). В этом конкретном использовании func в caller.cc, с этой конкретной реализацией boost, тип boost::bind на самом деле указан в ошибке компоновщика между ‹ и > (т.е. boost::_bi::bind_t...). Но явное создание экземпляра func для этого типа, вероятно, будет иметь проблемы с переносимостью.

person SCFrench    schedule 23.10.2008
comment
Извините, но это не должно быть проблемой. Я проделывал то же, что и он, много раз, как в Windows, так и в Linux, без проблем. - person Head Geek; 23.10.2008
comment
Я остаюсь при своем заявлении. посмотрите на корявый typedef в сообщении Мартина Йорка. Невозможно заменить myfunc на boost::function и заставить его работать. - person SCFrench; 24.10.2008
comment
@Head Geek: этот ответ касается «неизвестного типа», возвращаемого bind, что означает несколько вещей: во-первых, это не boost:: function‹›, а затем то, что он может быть изменен в любое время разработчиком библиотеки. поскольку он не является частью общедоступного интерфейса, они не обязаны возвращать тот же тип в следующем выпуске Boost. - person David Rodríguez - dribeas; 08.07.2009