Подтвердить что ты не робот

Использование свойств типа С++ 11 для обеспечения альтернативных встроенных реализаций

Является ли следующий шаблон кода разумным при использовании признаков в шаблоном коде, где обе альтернативные реализации всегда компилируются?

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

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 copy constructor won't throw
        }
        else
        {
            // some longer code with try/catch blocks and more complexity
        }
    }

    // many other methods
};

(Добавленная сложность частично обеспечивает сильную гарантию исключения.)

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

Если это не правильный способ сделать это, может кто-нибудь, пожалуйста, просветить меня до рекомендуемого синтаксиса?

4b9b3361

Ответ 1

[...] разумно ли ожидать, что компилятор устранит константно-ложные ветки и сделает вложение и т.д. для случая без исключения (где намного проще, чем в другом случае)?

Это может быть, но я бы не стал полагаться на это, потому что вы не можете его контролировать.


Если вы хотите удалить if/else, вы можете скрыть тип возвращаемого значения и очистить квалификатор 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)
    {}
};

Недостатки в том, что теперь у вас есть два шаблона функций-членов.
Не уверен, что он соответствует вашим требованиям.

Если вам разрешено использовать функции из С++ 17, if constexpr - это, вероятно, путь, и вам больше не нужно прерывать свой метод в двух функциях-членах.

Другой подход может быть основан на теге-диспетчеризации noexcept вашего типа.
В качестве примера:

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 - это не глагол, а сфайна, что-то звучит так хорошо.

Ответ 2

разумно ли ожидать, что компилятор устранит константно-ложные ветки и сделает вложение и т.д. для случая без исключения [...]?

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

Я все равно поеду с shenanigans SFINAE (на самом деле тег-диспетчеризация), что можно сделать очень легко в С++ 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 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
};

Ответ 3

Можно ли ожидать, что компилятор устранит константно-ложные ветки?

Да, исключение мертвого кода - одна из простейших оптимизаций.

... и сделать вложение и т.д. для случая без исключения?

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

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

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

Ответ 4

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

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

Если вы делаете такие вещи, как создавать переменные static или thread_local или создавать экземпляры символов, их все труднее устранить.

Вложения немного сложнее, потому что компиляторы в какой-то момент отказываются от вложения; чем сложнее код, тем вероятнее, что компилятор откажется, прежде чем вставлять его.

В С++ 17 вы можете обновить версию if до версии constexpr. Но в С++ 14 и 11 ваш код будет отлично. Это проще и легче читать, чем альтернативы.

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

Ответ 5

но разумно ли ожидать, что компилятор устранит ветки с постоянной ложью

Нет. Все ветки будут оцениваться компилятором. Вы можете попробовать использовать if constexpr из С++ 17.

То, что вы пытаетесь достичь, это SFINAE.

Ответ 6

Вы можете попытаться реализовать constexpr_if самостоятельно. Решение С++ 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()