C ++ Утилизация времени компиляции с интерфейсами C ++ - PullRequest
2 голосов
/ 28 августа 2011

Есть ли способ сделать что-то вроде следующего в C ++

template<typename TAnimal>
bool can_eat(TAnimal& animal) where bool TAnimal::IsAlive() exists
{
    return !animal.IsAlive();
}

//...

Duck duck;
assert(can_eat(duck) == true); //compiles

Wood wood;
can_eat(wood); // fails to compile because wood doesn't have IsAlive()

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

Ответы [ 6 ]

8 голосов
/ 28 августа 2011

Делайте , а не , используйте enable_if для выполнения ваших требований.Использование enable_if сделает функцию «исчезнувшей», что может сбить пользователя с толку.Типичным симптомом является сообщение об ошибке, например error: no matching function for call to <i>expression</i>.Это точно не говорит пользователю о том, что требование нарушено.

Вместо этого вам следует применять свои требования, используя static_assert, предполагая, что C ++ 0x.Если вы используете C ++ 03, то следует ли использовать эмуляцию static_assert (например, Boost's STATIC_ASSERT) или нет, так как это обычно означает обмен одного сообщения об ошибке на другое.

Контрастность:

// SFINAE for types that do not decay to int
template<
    typename T
    , typename = typename std::enable_if<
        std::is_same<
            typename std::decay<T>::type
            , int
        >::value
    >::type
>
void
f(T&&)
{}

// using static assert instead
template<
    typename T
>
void
g(T&&)
{
    static_assert( std::is_same<typename std::decay<T>::type, int>::value
                 , "Constraints violation" );
}

При использовании GCC я получаю следующую ошибку при выполнении f("violation") (оба сообщения содержат имя файла и номер строки):

error: no matching function for call to 'f(const char [10])'

С другой стороны, g("violation") выход:

error: static assertion failed: "Constraints violation"

Теперь представьте, что вы используете четкие, явные сообщения в своих утверждениях, такие как foo: parameter type must be CopyConstructible внутри шаблона foo.

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


То, что вы хотите сделать, легко достигается с помощью Boost.ConceptCheck .Однако для этого требуется написать внеплановый код: класс ограничений.Я также не думаю, что он использует static_assert там, где это возможно, поэтому сообщения об ошибках могут быть не такими хорошими.Это может быть изменено в будущем.

Другая возможность - использовать черты типа static_assert +.Что интересно в этом подходе, так это то, что в C ++ 0x библиотека содержит множество полезных особенностей, которые вы можете использовать прямо из коробки без написания внепланового кода.Еще более интересным является то, что использование признаков не ограничивается ограничениями записи, их также можно использовать с SFINAE для создания умных перегрузок.

Однако нет никакой черты, доступной для проверки,Тип поддерживает определенный член операции, возможно, из-за способа, которым C ++ обрабатывает имена функций.Мы также не можем использовать что-то вроде has_member<T, &T::member_to_test_for>, потому что это имело бы смысл только в том случае, если бы элемент, для которого мы тестировали, существовал в первую очередь (не считая таких вещей, как перегрузки и тот факт, что нам также необходимо передать подпись членаtrait).

Вот как можно преобразовать произвольное выражение в trait:

template<typename T>
struct void_ {
    typedef void type;
};

template<typename T>
struct trait {
private:
    typedef char yes[1];
    typedef char no[2];

    template<typename U>
    static
    yes&
    test(U&&
        , typename void_<decltype( std::declval<U&>().member() )>::type* = 0);

    static
    no&
    test(...);

public:
    static constexpr bool value = sizeof test(std::declval<T>()) == sizeof(yes);
};

Обратите внимание, насколько это существенно.Написание класса ограничений Boost.ConceptCheck может быть проще (но помните, что его нельзя использовать для SFINAE).

Произвольное выражение - std::declval<U&>().member().Здесь требования таковы, что при заданной lvalue ссылке U (или T для случая, когда признак истинен, если хотите), тогда вызов member() для него действителен.

Youтакже может проверить, что тип этого выражения (т. е. тип результата какой-либо перегрузки member был выбран для этого выражения) можно преобразовать в тип (не проверять, является ли этим типом;слишком строгие без уважительной причины).Это, однако, раздуло бы эту черту, снова сделав это в пользу класса ограничений.

Я не знаю способа сделать static_assert частью сигнатуры шаблона функции (кажется, это что-товы хотите), но он может появиться внутри шаблона класса.Boost.ConceptCheck также не поддерживает это.

5 голосов
/ 28 августа 2011

Это то, что концепции были предназначены для решения.

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

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

2 голосов
/ 28 августа 2011

Повышение предлагает BOOST_STATIC_ASSERT для этого. Только что утвержденная версия стандарта C ++ будет предлагать встроенную версию макроса static_assert.

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

1 голос
/ 28 августа 2011
where void TAnimal::IsAlive() exists

Полагаю, вы имеете в виду bool TAnimal::IsAlive()? Если это так, C ++ уже делает то, что вы просите. Если в Duck есть метод IsAlive (), он скомпилирует:

Duck duck;
assert(can_eat(duck) == true); //compiles

Если в Wood нет метода IsAlive (), он не скомпилируется:

Wood wood;
can_eat(wood); // fails to compile because wood doesn't have IsAlive()

Это то, что вы просите правильно?

0 голосов
/ 28 августа 2011

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

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

Я должен признать, что сам не использовал ничего из этого, но мне кажется, что вы должны использовать это для достижения того, чего хотите ...

0 голосов
/ 28 августа 2011

Вам не нужно ничего делать - просто опустите гипотетическое «где ... существует» из вашего примера, и это нормальный код C ++, который работает.

Если вы настаиваете на том, чтобы функция была доступна только при определенных условиях, вы можете попробовать объединить boost::enable_if с has_member здесь: http://lists.boost.org/Archives/boost/2002/03/27229.php

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

...