templates template - Шаблоны C++, которые принимают только определенные типы




function specialization (12)

В Java вы можете определить общий класс, который принимает только типы, которые расширяют класс по вашему выбору, например:

public class ObservableList<T extends List> {
  ...
}

Это делается с использованием ключевого слова «extends».

Есть ли простой простой эквивалент этому ключевому слову в C ++?


Answers

Это невозможно на простом C ++, но вы можете проверить параметры шаблона во время компиляции с помощью Concept Checking, например, используя BCCL Boost .


В C ++ это обычно необоснованно, как отметили другие ответы. В C ++ мы склонны определять общие типы, основанные на других ограничениях, отличных от «наследуемых от этого класса». Если вы действительно хотели это сделать, это довольно легко сделать в C ++ 11 и <type_traits> :

#include <type_traits>

template<typename T>
class observable_list {
    static_assert(std::is_base_of<list, T>::value, "T must inherit from list");
    // code here..
};

Это нарушает множество концепций, которые люди ожидают на C ++. Лучше использовать трюки, например, определять свои собственные черты. Например, возможно, observable_list хочет принять любой тип контейнера с параметром typedefs const_iterator и функцией begin и end member, которая возвращает const_iterator . Если вы ограничиваете это классами, которые наследуются от list то пользователь, который имеет свой собственный тип, который не наследуется от list но предоставляет эти функции-члены и typedefs, не сможет использовать ваш observable_list .

Есть два решения этой проблемы, одна из них - не сдерживать что-либо и полагаться на утиную печать. Большим преимуществом этого решения является то, что он включает в себя огромное количество ошибок, которые могут быть затруднены для пользователей. Другим решением является определение признаков для ограничения типа, предусмотренного для соответствия требованиям интерфейса. Большой конфликт для этого решения заключается в том, что он требует дополнительной записи, которая может рассматриваться как раздражающая. Однако положительная сторона заключается в том, что вы сможете написать свои собственные сообщения об ошибках a la static_assert .

Для полноты приведено решение вышеприведенного примера:

#include <type_traits>

template<typename...>
struct void_ {
    using type = void;
};

template<typename... Args>
using Void = typename void_<Args...>::type;

template<typename T, typename = void>
struct has_const_iterator : std::false_type {};

template<typename T>
struct has_const_iterator<T, Void<typename T::const_iterator>> : std::true_type {};

struct has_begin_end_impl {
    template<typename T, typename Begin = decltype(std::declval<const T&>().begin()),
                         typename End   = decltype(std::declval<const T&>().end())>
    static std::true_type test(int);
    template<typename...>
    static std::false_type test(...);
};

template<typename T>
struct has_begin_end : decltype(has_begin_end_impl::test<T>(0)) {};

template<typename T>
class observable_list {
    static_assert(has_const_iterator<T>::value, "Must have a const_iterator typedef");
    static_assert(has_begin_end<T>::value, "Must have begin and end member functions");
    // code here...
};

В приведенном выше примере представлено множество концепций, демонстрирующих возможности C ++ 11. Некоторые поисковые запросы для любопытных - это вариативные шаблоны, SFINAE, выражение SFINAE и черты типов.


Эквивалент, который принимает только типы T, полученные из типа List, выглядит так:

template<typename T, 
         typename std::enable_if<std::is_base_of<List, T>::value>::type* = nullptr>
class ObservableList
{
    // ...
};

Ну, вы можете создать свой шаблон, читающий что-то вроде этого:

template<typename T>
class ObservableList {
  std::list<T> contained_data;
};

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

Насколько я знаю, в текущем стандарте не существует конструкции, которая бы полностью отражала оператор оператора Java в полном объеме.

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

В C ++ 11 введение понятий должно сделать это проще, но я не думаю, что он будет делать именно то, что вы хотите.


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

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

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

С помощью современного компилятора у вас есть static_assert , который можно использовать вместо него.


Для таких проверок типа нет ключевого слова, но вы можете поместить некоторый код, который по крайней мере потерпит неудачу:

(1) Если вы хотите, чтобы шаблон функции принимал только параметры определенного базового класса X, назначьте ему ссылку X в своей функции. (2) Если вы хотите принимать функции, но не примитивы или наоборот, или хотите фильтровать классы другими способами, вызовите (пустую) вспомогательную функцию шаблона внутри вашей функции, которая определена только для классов, которые вы хотите принять.

Вы можете использовать (1) и (2) также в функциях-членах класса, чтобы заставить эти проверки типа для всего класса.

Вы можете, вероятно, поместить его в какой-нибудь умный Macro, чтобы облегчить вашу боль. :)


Есть ли простой простой эквивалент этому ключевому слову в C ++?

Нет.

В зависимости от того, что вы пытаетесь выполнить, могут быть адекватные (или даже лучшие) заменители.

Я просмотрел некоторый код STL (в Linux, я думаю, что это тот, который вытекает из реализации SGI). Он имеет «концептуальные утверждения»; например, если вам нужен тип, который понимает *x и ++x , это утверждение концепции будет содержать этот код в функции do-nothing (или что-то подобное). Это требует некоторых накладных расходов, поэтому было бы разумно поместить его в макрос, определение которого зависит от #ifdef debug .

Если отношение подкласса действительно то, о чем вы хотите знать, вы можете утверждать в конструкторе, что T instanceof list (за исключением того, что он написан «по-разному» на C ++). Таким образом, вы можете проверить свой выход из компилятора, не имея возможности проверить его для вас.


Насколько я знаю, в настоящее время это невозможно в C ++. Тем не менее, есть планы добавить функцию, называемую «понятия» в новый стандарт C ++ 0x, который обеспечивает функциональность, которую вы ищете. Эта статья в Википедии о концепциях C ++ объяснит это более подробно.

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


Я предлагаю использовать функцию статического утверждения Boost в сочетании с is_base_of из библиотеки Boost Type Traits:

template<typename T>
class ObservableList {
    BOOST_STATIC_ASSERT((is_base_of<List, T>::value)); //Yes, the double parentheses are needed, otherwise the comma will be seen as macro argument separator
    ...
};

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

template<typename T> class my_template;     // Declare, but don't define

// int is a valid type
template<> class my_template<int> {
    ...
};

// All pointer types are valid
template<typename T> class my_template<T*> {
    ...
};

// All other types are invalid, and will cause linker error messages.

[Незначительный EDIT 6/12/2013: использование объявленного, но не определенного шаблона приведет к компоновщику , а не компилятору, сообщения об ошибках.]


Резюме: Не делайте этого.

Ответ j_random_hacker подскажет вам, как это сделать. Однако я хотел бы также отметить, что вы не должны этого делать. Весь смысл шаблонов заключается в том, что они могут принимать любой совместимый тип, а ограничения типа стиля Java нарушают это.

Ограничения типа Java являются ошибкой, а не особенностью. Они существуют, потому что Java набирает стирание на generics, поэтому Java не может понять, как вызывать методы, основанные только на значения параметров типа.

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

Простой пример, демонстрирующий мощь шаблонов:

// Sum a vector of some type.
// Example:
// int total = sum({1,2,3,4,5});
template <typename T>
T sum(const vector<T>& vec) {
    T total = T();
    for (const T& x : vec) {
        total += x;
    }
    return total;
}

Эта функция суммы может суммировать вектор любого типа, который поддерживает правильные операции. Он работает как с примитивами типа int / long / float / double, так и с определенными пользователем числовыми типами, которые перегружают оператор + =. Heck, вы можете даже использовать эту функцию для объединения строк, так как они поддерживают + =.

Никакой бокс / распаковка примитивов не требуется.

Обратите внимание, что он также создает новые экземпляры T, используя T (). Это тривиально в C ++ с использованием неявных интерфейсов, но на самом деле это невозможно в Java с ограничениями типа.

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


Мы можем использовать std::is_base_of и std::enable_if :
( static_assert можно удалить, указанные выше классы могут быть реализованы или использоваться с помощью boost если мы не можем ссылаться на type_traits )

#include <type_traits>
#include <list>

class Base {};
class Derived: public Base {};

#if 0   // wrapper
template <class T> class MyClass /* where T:Base */ {
private:
    static_assert(std::is_base_of<Base, T>::value, "T is not derived from Base");
    typename std::enable_if<std::is_base_of<Base, T>::value, T>::type inner;
};
#elif 0 // base class
template <class T> class MyClass: /* where T:Base */
    protected std::enable_if<std::is_base_of<Base, T>::value, T>::type {
private:
    static_assert(std::is_base_of<Base, T>::value, "T is not derived from Base");
};
#elif 1 // list-of
template <class T> class MyClass /* where T:list<Base> */ {
    static_assert(std::is_base_of<Base, typename T::value_type>::value , "T::value_type is not derived from Base");
    typedef typename std::enable_if<std::is_base_of<Base, typename T::value_type>::value, T>::type base; 
    typedef typename std::enable_if<std::is_base_of<Base, typename T::value_type>::value, T>::type::value_type value_type;

};
#endif

int main() {
#if 0   // wrapper or base-class
    MyClass<Derived> derived;
    MyClass<Base> base;
//  error:
    MyClass<int> wrong;
#elif 1 // list-of
    MyClass<std::list<Derived>> derived;
    MyClass<std::list<Base>> base;
//  error:
    MyClass<std::list<int>> wrong;
#endif
//  all of the static_asserts if not commented out
//  or "error: no type named ‘type’ in ‘struct std::enable_if<false, ...>’ pointing to:
//  1. inner
//  2. MyClass
//  3. base + value_type
}

Просто из любопытства я взглянул на то, что происходит под капотом, и я использовал dtruss/strace на каждом тесте.

C ++

./a.out < in
Saw 6512403 lines in 8 seconds.  Crunch speed: 814050

sudo dtruss -c ./a.out < in

CALL                                        COUNT
__mac_syscall                                   1
<snip>
open                                            6
pread                                           8
mprotect                                       17
mmap                                           22
stat64                                         30
read_nocancel                               25958

питон

./a.py < in
Read 6512402 lines in 1 seconds. LPS: 6512402

sudo dtruss -c ./a.py < in

CALL                                        COUNT
__mac_syscall                                   1
<snip>
open                                            5
pread                                           8
mprotect                                       17
mmap                                           21
stat64                                         29




c++ templates