Использование признаков типа C++11 для обеспечения альтернативных встроенных реализаций
Является ли следующий шаблон кода разумным при использовании признаков в шаблонном коде, где обе альтернативные реализации всегда компилируемы?
Чтение кода кажется более ясным, чем выполнение других трюков для условной компиляции (но тогда, возможно, я просто недостаточно знаком с этими трюками).
template<typename T>
class X
{
void do_something() noexcept(std::is_nothrow_copy_constructible<T>::value)
{
if (std::is_nothrow_copy_constructible<T>::value)
{
// some short code that assumes T's copy constructor won't throw
}
else
{
// some longer code with try/catch blocks and more complexity
}
}
// many other methods
};
(дополнительная сложность отчасти заключается в том, чтобы обеспечить надежную гарантию исключения.)
Я знаю, что этот код будет работать , но разумно ли ожидать, что компилятор, чтобы исключить константы-ложные ветви и сделать inlining etc для случая noexcept (где гораздо проще, чем в другом случае)? Я надеюсь на что-то, что будет столь же эффективным в случае noexcept, как написание метода только с этим первым блоком в качестве тела (и наоборот, хотя я меньше беспокоюсь о сложном случае).
Если это не правильный способ, может кто-нибудь просветить меня относительно рекомендуемого синтаксиса?
6 ответов:
Это может быть, но я бы не стал полагаться на это, потому что вы не можете контролировать это.[...] разумно ли ожидать, что компилятор устранит ветви constant-false и сделает вставку etc для случая noexcept (где намного проще, чем в другом случае)?
Если вы хотите удалить
if/else
, Вы можете sfinae возвращаемый тип и очистить квалификаторnoexcept
.
В качестве примера:template<typename T> class X { template<typename U = T> std::enable_if_t<std::is_nothrow_copy_constructible<T>::value> do_something() noexcept(true) {} template<typename U = T> std::enable_if_t<not std::is_nothrow_copy_constructible<T>::value> do_something() noexcept(false) {} };
Недостатки заключаются в том, что теперь у вас есть две функции-члена шаблон.
Не уверен, что он соответствует вашим требованиям.Если вам разрешено использовать функции из C++17,
if constexpr
, вероятно, это правильный путь, и вам больше не нужно разбивать свой метод на две функции-члена.Другой подход может быть основан надиспетчеризации тегов
noexcept
ness вашего типа.
В качестве примера:template<typename T> class X { void do_something(std::true_type) noexcept(true) {} void do_something(std::false_type) noexcept(false) {} void do_something() noexcept(do_something(std::is_nothrow_copy_constructible<T>{})) { do_something(std::is_nothrow_copy_constructible<T>{}); } };
я знаю, sfinae не глагол, но для sfinae что-то звучит так хорошо.
Разумно ли ожидать, что компилятор устранит ветви constant-false и сделает inlining etc для случая noexcept [...]?
Да. Тем не менее, ветвь constant-false должна быть создана, что может привести или не привести к тому, что компилятор создаст набор символов, которые вам не нужны (а затем вам нужно будет полагаться на компоновщик, чтобы удалить их).
Я бы все равно пошел с SFINAE shenanigans (фактически tag-dispatching), что можно сделать очень легко в C++11.
template<typename T> class X { void do_something() noexcept(std::is_nothrow_copy_constructible<T>::value) { do_something_impl(std::is_nothrow_copy_constructible<T>() ); } void do_something_impl( std::true_type /* nothrow_copy_constructible */ ) { // some short code that assumes T's copy constructor won't throw } void do_something_impl( std::false_type /* nothrow_copy_constructible */) { // some longer code with try/catch blocks and more complexity } // many other methods };
Если вы собираетесь проверить наличие
nothrow_copy_constructor
во всех других методах, вы можете рассмотреть возможность специализации всего класса:template<typename T, class = std::is_nothrow_copy_constructible_t<T> > class X { //throw copy-ctor implementation }; template<typename T> class X<T, std::true_type> { // noexcept copy-ctor implementation };
Да, устранение мертвого кода - одна из простейших оптимизаций.Разумно ли ожидать, что компилятор устранит ветви constant-false?
... и сделать подстановку и т. д. В случае, как noexcept?
Моим первым побуждением было ответить:"Нет, на это нельзя полагаться, так как это зависит от того, где в потоке оптимизации находится входящий проход относительно шага устранения мертвого кода".
Но, поразмыслив, я не могу посмотрите, почему зрелый компилятор на достаточно высоком уровне оптимизации не будет выполнять удаление мертвого кода как до, так и после этапа встраивания. Так что это ожидание тоже должно быть разумным.
Однако угадывание в отношении оптимизаций никогда не бывает верным. Перейдите к простой реализации и получите правильно функционирующий код. Затем измерьте его эффективность и проверьте, были ли ваши предположения верны. Если бы их не было - перепроектирование реализации для вашей ситуации не займет много времени. значительно больше времени, чем если бы вы шли по гарантированному пути с самого начала.
Каждый зрелый компилятор выполняет удаление мертвого кода. Каждый зрелый компилятор обнаруживает постоянные ветви и мертвые коды другой ветви.
Вы можете создать функцию с десятком шаблонных аргументов, которая использует наивные проверки
if
в своем теле, и посмотреть на результат предположительно-проблемы не будет.Если вы делаете такие вещи, как создание переменных
static
илиthread_local
или создание экземпляров символов, их все труднее устранить.Инлайнинг немного сложнее, потому что компиляторы склонны отказываться от инлайнинга в какой-то момент; чем сложнее код, тем больше вероятность, что компилятор откажется от него до инлайнинга.
В C++17 вы можете обновить свой
if
до версииconstexpr
. Но в C++14 и 11 ваш код будет работать просто отлично. Это проще и легче читать, чем альтернативы.Он несколько хрупок, но если он ломается, то обычно делает это во время компиляции шумным способом.
Но разумно ли ожидать, что компилятор устранит ветви constant-false
Нет. Все ветви будут оцениваться компилятором. Вы можете попробовать использовать
То, чего вы пытаетесь достичь, - это SFINAE.if constexpr
из c++17.
Вы можете попробовать реализовать
constexpr_if
самостоятельно. решение c++11 может выглядеть следующим образом:#include <iostream> #include <type_traits> template <bool V> struct constexpr_if { template <class Lambda, class... Args> static int then(Lambda lambda, Args... args) { return 0; } }; template <> struct constexpr_if<true> { template <class Lambda, class... Args> static auto then(Lambda lambda, Args... args) -> decltype(lambda(args...)) { return lambda(args...); } static int then(...) { return 0; } }; struct B { B() {} B(const B &) noexcept {} void do_something() { std::cout << "B::do_something()" << std::endl; } }; struct C { C() {} C(const C &) noexcept {} void do_something_else() { std::cout << "C::do_something_else()" << std::endl; } }; struct D { D() {} D(const D &) throw(int) {} void do_something_else() { std::cout << "D::do_something_else()" << std::endl; } }; template <class T> struct X { void do_something() { T t; constexpr_if<std::is_nothrow_copy_constructible<T>::value>::then([](B &b) { b.do_something(); }, t); constexpr_if<std::is_nothrow_copy_constructible<T>::value>::then([](C &c) { c.do_something_else(); }, t); constexpr_if<!std::is_nothrow_copy_constructible<T>::value>::then([](D &d) { d.do_something_else(); }, t); } }; int main() { X<B> x; x.do_something(); X<C> xx; xx.do_something(); X<D> xxx; xxx.do_something(); }
Вывод:
B::do_something() C::do_something_else() D::do_something_else()