Можно ли это легально сделать на C ?

#c #templates #language-specifications #one-definition-rule

#c #шаблоны #язык-спецификации #одно определение-правило

Вопрос:

Примечание: следующий код незаконен, но соответствующий компилятор не обязан отклонять его (а некоторые этого не делают).

В библиотеке, с которой я работаю, у меня есть объявление функции шаблона для Foo и определение функции шаблона для Bar в foobar.h :

 template<class C> int Foo();

template<class C> int Bar() {
  return Something(  Foo<C>()  );
}
  

Предполагается, что другой код сможет использовать это следующим образом:

 #include "foobar.h"

int main() {
  Bar<MyClass>();
  return 0;
}

// Ideally, the usage (above) and the definition (below)
// would/could be in different translation units. 

template<> int Foo<MyClass>() { return 5; }
  

Вопрос: Есть ли способ сделать эту работу также легальной?


Проблема в том (если я правильно понимаю), что, несмотря на компиляцию, это технически незаконно: это нарушает ODR, потому что как явная специализация, так и использование Bar<MyClass> считаются определениями, несмотря на то, что в случае использования нет тела для работы.

Причины, по которым я хочу использовать этот шаблон для параметризации Foo , заключаются в том, что в соответствии с руководством по стилю, которому я обязан следовать, единственный способ гарантировать, что что-либо лексически включено перед определением Bar , — это включить его с помощью foobar.h . Но (по причине, я полагаю, мне не нужно объяснять) это не для начала.

Комментарии:

1. Что должно означать это последнее определение Foo ? Абсолютно не понятно, что вы пытаетесь сделать. Предоставьте более подробную информацию.

2. Почему бы не указать int(*)(void) указатель на функцию в качестве параметра шаблона на Bar вместо класса C? template <int(*FOO)()> int Bar() { return Something(FOO()); } . Тогда пользователь может указать, какую функцию вызывать напрямую, а не косвенно, специализируясь Foo на MyClass . И тогда указатель на функцию можно было бы заменить классом functor, если предпочтительнее.

3. Я думаю, что шаблоны бесплатных функций обычно следует объявлять inline , чтобы обойти ODR.

4. Не уверен насчет ODR, но код, похоже, нарушает 14.7.3 / 6: «Если шаблон … является явно специализированным, тогда эта специализация должна быть объявлена перед первым использованием этой специализации, что привело бы к неявному созданию экземпляра … »

5. Мысль: у вас нет специализации, которая должна быть включена перед определением Bar, непосредственно перед первым использованием Bar<MyClass> должно быть достаточно, потому что точка создания есть. Т.Е. наличие специализации, объявленной (даже не определенной) раньше, main() было бы законно, я полагаю. Тоже не вариант?

Ответ №1:

Есть ли способ сделать эту работу также легальной?

Да, объявите специализацию перед ее использованием. Простое изменение порядка специализации и main в вашем файле сделало бы это в приведенном вами примере. Однако в приведенном примере вам все равно придется убедиться, что ни один другой TU не использовал специализацию без ее объявления.

Как правило, эти специализации должны быть объявлены в заголовке.

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

Комментарии:

1. Это можно было бы заставить работать в случае, с которым я столкнулся, но не очень хорошо обобщает. (смотрите комментарий, который я добавил во 2-м блоке кода)

2. @BCS: «Идеальная» ситуация в вашем комментарии категорически невозможна: вещи должны быть объявлены, прежде чем их можно будет использовать, и объявление для этой функции не может отсутствовать в TU, где она используется.

3. @BCS: Сравните с ситуацией попытки void f() { this_function_not_declared(); } без объявления вызываемой функции в этом TU.

4. Да, они должны быть объявлены до их использования, но что я хочу для идеального решения, так это использовать шаблонную функцию, которая только (и, в этом TU, только будет) объявлена, но не определена .

5. @BCS: Это нормально. Объявляйте перед использованием, определите это один раз.

Ответ №2:

Это незаконно, поскольку невозможно специализировать функции шаблона.

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

 template< typename T >
struct foo
{
  static int doSomething() {return 0;}
};
template< >
struct foo<int>
{
  static int doSomething() {return 5;}
};
  

Комментарии:

1. Конечно, шаблоны функций могут быть специализированными, просто не частично специализированными.

2. Мне нравится, как do он окрашен в синий цвет. 🙂

3. иногда мне хочется, чтобы люди не голосовали, если они не понимают… -1 что говорит @qauamrana.

Ответ №3:

Это компилирует ссылки и запускается как с VS2008, так и с gcc 4.5.2:

 template<class C> int Foo();

int Something(int i ){ return i; }

template<class C> int Bar() {
    return Something(  Foo<C>()  );
}

class MyClass{};
class FooClass{};

// Update:
// Declaration of a specialisation.
template<> int Foo<MyClass>();

int Zoo(){
    return Something(  Foo<MyClass>()  );
}

#include <iostream>

int main() {
    std::cout << Bar<MyClass>() << std::endl;
    std::cout << Zoo() << std::endl;
    std::cout << Bar<FooClass>() << std::endl;
    return 0;
}

// Definitions of specialisations.
template<> int Foo<MyClass>() { return 5; }
template<> int Foo<FooClass>() { return 6; }
  

Результат таков:

 5
5
6
  

Причина, по которой это работает, заключается в том, что, несмотря на то, что функция шаблона только объявлена, у нее есть два необходимых определения, доступных компоновщику. Foo
Во время компиляции, когда компилятор видит определение main , он может создавать специализации Bar , но не может создавать специализации Foo . В этом случае он просто создает вызовы функций Foo<MyClass>() и Foo<FooClass>() .
Позже компилятор находит специализации, Foo которые он компилирует, оставляет в объектном файле, но больше ничего не делает с ними в данный момент. Когда компоновщик запускается, он находит все, что ему нужно.

Обновить:
Итак, я не знаю, почему это незаконно, но по какой-то причине это, похоже, компилируется и запускается.

Комментарии:

1. Ваше описание — это именно то поведение, которое мне нужно. OTOH, хотя он может компилироваться и запускаться, это незаконный код: permalink.gmane.org/gmane.comp.compilers.llvm.bugs/10472 Я думаю, что и GCC, и clang отвергнут это.

2. @BCS: Я думаю, что этот код легален , потому что нет определения Foo того, когда компилируется main. Если есть, gcc 4.5.2 возвращается с простой ошибкой множественного определения.

3. Если Bar не является шаблонизированным и выполняется вызов Foo для прямого указания типа, то это генерирует сообщение об ошибке (т. е. это незаконно). Если я чего-то не упускаю, это не должно иметь значения с точки зрения законности (т. Е. Оба являются незаконными), потому что в любом случае вызов Foo должен быть обработан до определения Foo<MyClass> .