Могу ли я исключить некоторые методы из создания шаблона вручную? - PullRequest
5 голосов
/ 22 января 2012

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

Теперь мы также делаем много ручного создания шаблона. Отчасти это связано с тем, что методы вынуждены компилятору проверять синтаксис методов. Это также уменьшает время компиляции для пользователя библиотеки. Проблема в том, что статические утверждения всегда выполняются, и, следовательно, мы не можем вручную создать экземпляр класса шаблона, о котором идет речь.

Есть ли обходной путь для этого?

РЕДАКТИРОВАТЬ: Чтобы сделать это более понятным, вот пример (явное создание экземпляра в этом случае не будет работать на someFunc1 ():

* * 1010

EDIT2: Вот еще один пример. На этот раз вы можете скомпилировать его, чтобы понять, что я имею в виду. Сначала скомпилируйте сразу. Код должен скомпилироваться. Тогда Раскомментируйте [2] и статическое утверждение должно сработать. Теперь закомментируйте [2] и Раскомментируйте [1] . Статическое утверждение сработает, потому что вы явно создаете экземпляр шаблона. Я хочу избежать удаления явной реализации из-за преимуществ, которые идут с ним (преимущества см. Выше).

namespace Loki
{
  template<int> struct CompileTimeError;
  template<> struct CompileTimeError<true> {};
}

#define LOKI_STATIC_CHECK(expr, msg) \
    { Loki::CompileTimeError<((expr) != 0)> ERROR_##msg; (void)ERROR_##msg; }

template <typename T>
class foo
{
public:

  void func() {}
  void func1() { LOKI_STATIC_CHECK(sizeof(T) == 4, Assertion_error); }
};

template foo<int>;
//template foo<double>; // [1]

int main()
{
  foo<int> a;
  a.func1();

  foo<double> b;
  //b.func1(); //[2]

  return 0;
}

Ответы [ 4 ]

3 голосов
/ 22 января 2012

Вы не можете иметь и то и другое: у вас не может быть статического утверждения, чтобы предотвратить создание экземпляров и явного создания экземпляра типа!Это очевидное противоречие.Однако у вас может быть условно включенная функциональность, даже если это немного болезненно: если определенная функция-член не должна поддерживаться для определенных типов, вы можете переместить эту функцию в базовый класс, который условно имеет ее.,Таким образом, вы не будете использовать статическое утверждение, а просто удалите функцию-член.Я понимаю, что это создает другие интересные проблемы, например, в отношении расположения переменных-членов, но я думаю, что в контексте, который вы описываете, это лучшее, что вы можете получить.

Вот быстрый пример того, какможет выглядеть так:

template <typename T, bool = std::numeric_limits<T>::is_integer> struct foo_base;
template <typename T> struct foo_base<T, false> { /* intentionally left blank */ };
template <typename T> struct foo_base<T, true> { void foo() { /*...*/ } };

template <typename T>
struct Foo: foo_base<T> { /* .... */ };

template struct Foo<int>;    // will have foo()
template struct Foo<double>; // will not have foo()
2 голосов
/ 22 января 2012

Хорошо, поэтому, если вы форсируете создание экземпляров всех методов, используя явное создание экземпляров, вы не можете избежать каких-либо трюков времени компиляции, чтобы предотвратить создание экземпляров методов, вызывающих сбой, таких как enable_if. Было бы достаточно легко перенести ошибку во время выполнения, но это нежелательно.

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

template<typename T>
struct Foo {
    void bar() {
        std::cout << "bar\n";
    }
    void baz() {
        std:: cout << "baz\n";
    }
};

template<> void Foo<int>::baz(); // use of Foo<int>::baz() will resolve to this specialization, and linking will fail 

template struct Foo<int>;
template struct Foo<char>;

int main() {
    Foo<int> f;
    f.bar();
    // f.baz(); // uncommenting this line results in an ugly link time error
    Foo<char> b;
    b.bar();
    b.baz();  // works with Foo<char>
}

Статические утверждения больше не помогают выдавать хорошие сообщения об ошибках, когда в клиентском коде допущена ошибка, но вы можете оставить их, потому что они сработают, если вы забудете указать специализацию.

1 голос
/ 22 января 2012

enable_if - это гибкий механизм для точного таргетинга по шаблонным методам, возможно, именно то, что вам нужно.Пример:

#include <string>
#include <iostream>

#include <boost/utility.hpp>
#include <boost/type_traits.hpp>
#include <boost/static_assert.hpp>

template <class T> class mywrapper
{
  T _value;

  template <class V>
  typename boost::enable_if<boost::is_scalar<V>, void>::type printval_(V const& value)
  {
    BOOST_STATIC_ASSERT(boost::is_scalar<V>::value);
    std::cout << "scalar: " << value << std::endl;
  }

  template <class V>
  typename boost::enable_if<boost::is_compound<V>, void>::type printval_(V const& value)
  {
    BOOST_STATIC_ASSERT(boost::is_compound<V>::value);
    std::cout << "compound: " << value << std::endl;
  }

public:
  mywrapper(T const& value):_value(value) { }
  void printval() { printval_(_value); }
};

template class mywrapper<int>;
template class mywrapper<std::string>;

int main()
{
  mywrapper<int> ival(333);
  mywrapper<std::string> sval("test");

  ival.printval();
  sval.printval();
  return 0;
}
0 голосов
/ 10 апреля 2012

У меня не было возможности протестировать enable_if, как предложено bobah, но я нашел решение, которое не требует повышения и которое в достаточной степени удовлетворяет моим первоначальным требованиям (я говорю хорошо а не full , поясню в конце)

Решение состоит в том, чтобы поместить фиктивный шаблон в код, который не будет работать, если он будет скомпилирован для одних выбранных типов и подходит для других.Итак:

struct dummyStruct {};

#define DUMMY_TEMP typename dummy
#define DUMMY_PARAM dummyStruct

namespace Loki
{
  template<int> struct CompileTimeError;
  template<> struct CompileTimeError<true> {};
}

#define LOKI_STATIC_CHECK(expr, msg) \
{ Loki::CompileTimeError<((expr) != 0)> ERROR_##msg; (void)ERROR_##msg; }

template <typename T>
class foo
{
public:

  void func() {}
  template <typename T_Dummy>
  void func1() { LOKI_STATIC_CHECK(sizeof(T) == 4, Assertion_error); }
};

template foo<int>;
template foo<double>; // [1]

int main()
{
  foo<int> a;
  a.func1<DUMMY_PARAM>();

  foo<double> b;
  //b.func1<DUMMY_PARAM>(); //[2] - this is a static error

  return 0;
}

Во всем коде моего шаблона функции такого типа (т.е. те, которые имеют статические утверждения ИЛИ работают с одними типами и могут не работать с другими при использовании черт типа [в этом случаевыбор нескольких различных функций для разных типов]) скрыты от клиента.Поэтому в моей реализации добавление дополнительного dummy parameter - это хороший компромисс.

В качестве бонуса, я сообщаю, что эта функция предназначена для использования только определенными типами.Кроме того, моя первоначальная проблема явного создания экземпляров решается с помощью этого простого метода.

...