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


template <typename T>
void f (T t)
{
    // Завладели экземпляром `t` типа `T`.
    ...

    // Хочешь — переноси.
    g(std::move(t));

    // Не хочешь — не переноси.
    ...
}

Но есть один нюанс: требуется, чтобы принимаемый объект был строго rvalue. Следовательно, нужно:


  1. Сообщать об ошибке компиляции при попытке передать lvalue.
  2. Избежать лишнего вызова конструктора при создании объекта на стеке.

А вот это уже сложнее сделать.


Поясню.


Требования к входным аргументам


Допустим, мы хотим обратного, то есть чтобы функция принимала только lvalue и не компилировалась, если ей на вход подаётся rvalue. Для этого в языке присутствует специальный синтаксис:


template <typename T>
void f (T & t);

Такая запись означает, что функция f принимает lvalue-ссылку на объект типа T. При этом заранее не оговариваются cv-квалификаторы. Это может быть и ссылка на константу, и ссылка на неконстанту, и любые другие варианты.


Но ссылкой на rvalue она быть не может: если передать в функцию f ссылку на rvalue, то программа не скомпилируется:


template <typename T>
void f (T &) {}

int main ()
{
    auto x = 1;
    f(x); // Всё хорошо, T = int.

    const auto y = 2;
    f(y); // Всё хорошо, T = const int.

    f(6.1); // Ошибка компиляции.
}

Может, есть синтаксис и для обратного случая, когда нужно принимать только rvalue и сообщать об ошибке при передаче lvalue?


К сожалению, нет.


Единственная возможность принять rvalue-ссылку на произвольный объект — это сквозная ссылка (forwarding reference):


template <typename T>
void f (T && t);

Но сквозная ссылка может быть ссылкой как на rvalue, так и на lvalue. Следовательно, нужного эффекта мы пока не добились.


Добиться нужного эффекта можно при помощи механизма SFINAE, но он достаточно громоздкий и неудобный как для написания, так и для чтения:


#include <type_traits>

template <typename T,
    typename = std::enable_if_t<std::is_rvalue_reference<T &&>::value>>
void f (T &&) {}

int main ()
{
    auto x = 1;
    f(x); // Ошибка компиляции.

    f(std::move(x)); // Всё хорошо.

    f(6.1); // Всё хорошо.
}

А чего бы на самом деле хотелось?


Хотелось бы вот такой записи:


template <typename T>
void f (rvalue<T> t);

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


Первая мысль, которая приходит в голову, — это создать псевдоним типа:


template <typename T>
using rvalue = T &&;

Но такая штука, к несчастью, не сработает, потому что подстановка псевдонима происходит до вывода типа шаблона, поэтому в данной ситуации запись rvalue<T> в аргументах функции полностью эквивалентна записи T &&.


Скрытый текст

Забавно, что из-за ошибки в системе вывода типов компилятора Clang (версию точно не помню, кажется, 3.6) этот вариант "сработал". В компиляторе GCC этой ошибки не было, поэтому поначалу мой затуманенный безумной идеей разум решил, что ошибка не в Кланге, а в Гэцэцэ. Но, проведя, небольшое расследование, я понял, что это не так. А через некоторое время и в Кланге эту ошибку исправили.


Ещё одна идея — по сути, аналогичная, — которая может прийти в голову знатоку шаблонного метапрограммирования — это написать следующий код:


template <typename T>
struct rvalue_t
{
    using type = T &&;
};

template <typename T>
using rvalue = typename rvalue_t<T>::type;

К структуре rvalue_t можно было бы припилить SFINAE, которое отваливалось бы, если бы T было ссылкой на lvalue.


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


Я очень расстроился и на время забросил эту идею.


Возвращение


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


Немного поразмыслив, я сформулировал "требования":


  1. Должен работать механизм вывода типа.
  2. Должна быть возможность натравливать SFINAE-проверки на выводимый тип.

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


Тогда возникает закономерный вопрос: можно ли натравливать SFINAE на псевдонимы типов?


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


template <typename T,
    typename = std::enable_if_t<std::is_rvalue_reference<T &&>::value>>
using rvalue = T &&;

Наконец-то получаем и требуемый интерфейс, и требуемое поведение:


template <typename T>
void f (rvalue<T>) {}

int main ()
{
    auto x = 1;
    f(x); // Ошибка компиляции.

    f(std::move(x)); // Всё хорошо.

    f(6.1); // Всё хорошо.
}

Победа.


Концепты


Внимательный читатель негодует: "Так где же тут концепты-то?".


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


template <typename I,
    typename = std::enable_if_t<std::is_integral<I>::value>>
using Integer = I;

template <typename I>
void g (Integer<I> t);

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


int main ()
{
    g(1); // Всё хорошо.
    g(1.2); // Ошибка компиляции.
}

Что ещё можно сделать?


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


template <Integer I>
void g (I n);

Для этого воспользуемся, кхм, макроснёй:


#define Integer(I) typename I, typename = Integer<I>

Получим возможность писать следующий код:


template <Integer(I)>
void g (I n);

На этом возможности данной техники, пожалуй, заканчиваются.


Недостатки


Если вспомнить название статьи, то можно подумать, что у этой техники есть какие-то недостатки.


Таки да. Есть.


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


template <typename I>
void g (Integer<I>) {}

template <typename I>
void g (Floating<I>) {}

и будет выдавать ошибку о переопределении функции g.


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


Выводы


Приведённая техника — назовём её техникой фильтрующего псевдонима типов — имеет достаточно ограниченную область применения.


Но в тех случаях, когда она применима, она открывает программисту достаточно неплохие возможности для чёткого выражения намерения в коде.


Считаю, что она имеет право на жизнь. Лично я пользуюсь. И не жалею.


Ссылки по теме


  1. Библиотека "Boost Concept Check"
  2. Концепты из прототипа библиотеки диапазонов "range-v3"
  3. Библиотека "TICK"
  4. Статья "Concepts Without Concepts"
Поделиться с друзьями
-->

Комментарии (50)


  1. Sharkow
    05.07.2016 13:34
    +4

    Зачем нужен запрет передавать lvalue? Как архитектурное решение — кажется неудачным.


    1. izvolov
      05.07.2016 13:37
      +4

      Зачем нужен запрет передавать lvalue?

      Подчеркнуть, что у входного объекта нет других владельцев.


      Как архитектурное решение — кажется неудачным.

      Почему?


  1. bfDeveloper
    05.07.2016 14:00
    +2

    Божественно. Особенно радует, что это не экран шаблонного кода, а вполне вменяемые 2 строки. Удачно отделяется концепт с условием от определения самого метода. Всё таки даже без концептов С++11 силён.
    Долго соображал над конструкцией typename = std::enable_if_t<...>. Я правильно понял, что это просто безымянный аргумент шаблона для using? А то на первый взгляд выглядит как специальный синтаксис.


    1. AndreySu
      05.07.2016 14:11
      +3

      да, и с паттерн матчингом который отключит компиляцию если тип не std::is_integral


    1. izvolov
      05.07.2016 14:20
      +2

      Я правильно понял, что это просто безымянный аргумент шаблона для using?

      Совершенно верно.
      Это безымянный аргумент шаблона со значением по-умолчанию.


      1. VioletGiraffe
        05.07.2016 16:10

        Какой смысл в безымянных шаблонных параметрах? Зачем это вообще компилируется, почему не синтаксическая ошибка?


        1. izvolov
          05.07.2016 16:13
          +4

          Какой смысл в безымянных аргументах функции? Зачем это вообще компилируется, почему не синтаксическая ошибка?


          1. monah_tuk
            06.07.2016 11:59
            +1

            Встроенная в мозг дифалка не сразу уловила разницу, нужно было хотя бы жирным выделить "аргументах функции" :)


        1. Overlordff
          05.07.2016 16:33
          +5

          Смысл безымянного параметра в названии — нам не интересно его имя, мы его далее никак не используем. А сам параметр нужен для SFINAE


  1. Unrul
    05.07.2016 14:24
    +5

    Использовать перегрузку функций по концептам можно с помощью следующего способа:

    template <typename T>
    auto foo(T i) -> enable_if_t<is_integral_v<T>, void> {
        printf("Int\n");
    }
    
    template <typename T>
    auto foo(T i) -> enable_if_t<is_floating_point_v<T>, void> {
        printf("Float\n");
    }
    
    template <typename T>
    auto foo(T i) -> enable_if_t<is_class_v<T>, void> {
        printf("Struct\n");
    }
    
    int main() {
        foo(1);
        foo(1u);
        foo(1.2);
        foo(1.2f);
        foo(make_tuple(1));
        return 0;
    }
    


    Или так:

    template <typename T, typename = enable_if_t<is_integral_v<T>>>
    using IsInteger = integral_constant<int, 0>;
    
    template <typename T, typename = enable_if_t<is_floating_point_v<T>>>
    using IsFloat = integral_constant<int, 1>;
    
    template <typename T, typename = enable_if_t<is_class_v<T>>>
    using IsStruct = integral_constant<int, 2>;
    
    template <typename T>
    auto foo(T i, IsInteger<T> = {}) -> void {
        printf("Int\n");
    }
    
    template <typename T>
    auto foo(T i, IsFloat<T> = {}) -> void {
        printf("Float\n");
    }
    
    template <typename T>
    auto foo(T i, IsStruct<T> = {}) -> void {
        printf("Struct\n");
    }
    


    Не так элегантно, но тем не менее. Будем надеяться, что концепты всё таки включат в стандарт когда-нибудь.


    1. izvolov
      05.07.2016 14:40

      Если хочется более полноценной работы с концептами, то нужно смотреть в сторону имеющихся библиотек (см. ссылки в конце статьи).


      Смысл приведённой техники именно в простоте и удобочитаемости.


      1. DistortNeo
        05.07.2016 16:34
        +2

        Лучше не использовать продвинутые заменители концептов до их появления в стандарте.

        Причины простые:
        1. Крайне неинформативный вывод сообщений об ошибках в шаблонах.
        2. Замедление скорости компиляции.
        3. Не всегда очевидна логика работы шаблонных конструкций без вдумчивого анализа кода.
        4. В конце концов, монструозные конструкции ломают IDE, в результате чего IDE превращается просто в редактор с подсветкой синтаксиса.

        Указанный в публикации пример — исключение, подтверждающее правило.

        P.S. Я бы просто воткнул в функцию static_assert.


        1. Unrul
          05.07.2016 18:58

          +1, static_assert будет наиболее предпочтителен, если не нужно использовать перегрузку.


        1. monah_tuk
          06.07.2016 12:23
          +2

          Замедление скорости компиляции.

          не думаю, что концепты сильно её ускорят, скорее наоборот. Ну т.е. код с одинаковым набором ограничений при помощи концептов и SFINAE думаю будет компилироваться со сравнительной скоростью. Нужно попробовать сравнить: в gcc-6 концепты завезли.


  1. Shamov
    05.07.2016 16:42
    +1

    Синтаксис настолько близок к настоящим концептам, что можно попробовать сделать универсальные макросы, которые будут использовать эту технику при отсутствии поддержки концептов в компиляторе. Если это удастся, то можно будет писать такой код, который будет компилиться как на нормальном компиляторе, так и на «concept-enabled» gcc.


  1. ulysses
    05.07.2016 17:20

    Поздравляю, вы переизобрели кусочек Concepts Lite! Которые, как вы упомянули, надысь выбросили из C++17. Вы можете сравнить свою логику и логику Саттона (Andrew Sutton), если включите какой-нибудь его доклад на Ютубе. Идея та же: CL это лёгкая обёртка над enable_if. Проблем несколько. Первая порция, которая касается вашего решения и не касается его реализации на уровне компилятора: это, как тоже сказали выше, время компиляции и, более важно, информативность ошибок (особенно в вашей версии с макросом она будет жуткой, думаю). Саттон специально отмечает, что на уровне компилятора это лучше решать.


    Про перегрузку вы сами написали. Есть ещё проблема, которая касается и вашего решения, и CL: нет проверки ограничений внутри тела шаблона. То есть вы можете выставить какой-то концепт-интерфейс, но внутри шаблона использовать больше, чем затребовали в этом интерфейсе (по недосмотру, который не так уж нереален в таком тяжёлом и синтаксически и семантически языке как C++). Конечно, ошибка будет на стадии компиляции. Но всё равно досадно. Особенно если это библиотека и на неё напорется какой-то неискушённый пользователь этой библиотеки.


    1. izvolov
      05.07.2016 17:53
      +2

      Я не понял вашей мысли.


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


      Я просто сымитировл часть функциональности, которая (и я об этом открыто говорю в статье) не даёт никаких преимуществ над другими методами пред-C++17, за исключением удобочитаемости.


      1. ulysses
        05.07.2016 22:49

        Я ни в чём никого не упрекаю. Моя мысль такая, что прослушав пару докладов Саттона одно или двухгодичной давности можно было бы написать вариант с определением rvalue через enable_if+type_traits довольно быстро. Потому что там это всё разжёвывается хорошо. Ну и компилятор писать я вас ни в коем случае не призываю. Просто пишу о пользе просмотра докладов ведущих специалистов.


        1. izvolov
          05.07.2016 23:18

          Можно ссылки на доклады, о которых идёт речь?


          1. ulysses
            09.07.2016 20:09
            +1

            1. izvolov
              10.07.2016 11:55

              Я посмотрел эти доклады.


              В них прекрасно рассказывается об "упрощённых концептах", но о технике, про которую речь в моей публикации, там ничего не говорится.


  1. DmitryBabokin
    05.07.2016 17:27
    +5

    А почему не объявить вот так?

    void g(T &t) = delete;
    void g(T &&t);
    

    Тогда lvalue замапится на первый вариант и выдаст ошибку компиляции.


    1. izvolov
      05.07.2016 18:10

      Преимущества записи rvalue<T> лично я вижу следующие:


      1. Она явно выражает намерение автора.


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


      2. Функция становится самодостаточной.


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



      1. DmitryBabokin
        05.07.2016 18:18

        Согласен. Но как решение для мира, где нет концептов, должно вполне годиться.


    1. bfDeveloper
      05.07.2016 18:26

      с lvalue gcc говорит, что
      error: call of overloaded 'bar(int&)' is ambiguous
      bar(a);
      ^

      Потому что обе сигнатуры — одно и то же для перегрузок. То есть всё работает, но ошибка совсем не информативна.
      P.S. Лучше воспользоваться не =delete, а static_assert (false, «lvalue is not supported»);


      1. EvgK
        11.11.2016 22:14

        В таких экспериментах обычно как раз и генерируют по одному фотону, потому что сопоставление сигнального и соответвующего ему холостого ведется на основе времени (сигнальный испустили во время Х, холостой детектировали в Х + А\с (где А — длина пути до детектора). Наложение картин от разных групп фотонов обойти действительно никак нельзя — информацию о том какой фотон принадлежит какой группе мы получаем только после детектирования холостого партнера.


      1. DmitryBabokin
        05.07.2016 19:12

        В том-то и дело, что не одно и тоже, в этом и фишка.

        void g(int &i) = delete;
        void g(int &&i);
        
        void foo(int i) {
          g(i);
          g(5);
        }
        


        clang 3.8:
        > clang++ -c foo.cpp -std=c++11
        foo.cpp:5:3: error: call to deleted function 'g'
        g(i);
        ^
        foo.cpp:1:6: note: candidate function has been explicitly deleted
        void g(int &i) = delete;
        ^
        foo.cpp:2:6: note: candidate function not viable: no known conversion from 'int' to 'int &&' for 1st argument
        void g(int &&i);
        ^
        1 error generated.

        gcc 5.3.1:
        > g++ -c foo.cpp -std=c++11
        foo.cpp: In function ‘void foo(int)’:
        foo.cpp:5:6: error: use of deleted function ‘void g(int&)’
        g(i);
        ^
        foo.cpp:1:6: note: declared here
        void g(int &i) = delete;
        ^


      1. 0xd34df00d
        09.07.2016 22:43
        +2

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

        Можно, впрочем, обойти костылём.


  1. horo
    05.07.2016 18:11
    +1

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

    static_assert(std::is_rvalue_reference <T &&>::value,"Message here");


    1. izvolov
      05.07.2016 18:28
      +3

      Явное лучше неявного.


      Когда я пишу rvalue<T>, я сообщаю читателю моего кода, что у меня здесь на входе всегда rvalue.
      Если же я пишу обычную сквозную ссылку и static_assert внутри функции, то читателю сложнее понять моё намерение. Потому что ему ещё нужно дочитать до этой проверки. А потом ему это нужно постоянно помнить, чтобы случайно не воткнуть forward вместо move.


      Так же, как и использование стандартных алгоритмов вместо циклов: мы сообщаем читателю что мы хотели сделать, а не как мы это сделали.


      См. также комментарий выше.


  1. VoidEx
    05.07.2016 20:21

    Интересно, спасибо


  1. XoJIoD
    11.11.2016 23:32
    +1

    Ну, раз уж речь зашла, то позвольте еще вопрос) В эксперименте с ластиком, если мы удалим маркеры со всех холостых фотонов, но после того, как сигнальные дошли до экрана, эти сигнальные ведь также будут показывать отсутствие интерференции?


    1. EvgK
      11.11.2016 23:54

      Я не физик, поэтому могу ошибаться. Но насколько я понимаю, в эксперименте с ластиком у нас 4 детектора: D1, D2, D3, D4. Если фотон попал в D1 или D2 — информация о пути «стерта». Если мы удалим маркеры со всех фотонов, они все попадут в D1 или D2. Но важный момент в том, что мы получим ДВЕ интерференционные картины (для фотонов из D1 и D2 соответственно), причем пики одной совпадают с впадинами другой. Соответственно эти две картины в сумме дают обычное хаотическое распределение (которое мы и видим на экране). Вот тут: https://en.wikipedia.org/wiki/Delayed_choice_quantum_eraser есть картинка, показывающая как выглядят результаты (Figure 4). Видно, что если сложить R01 и R02 — получим хаотический набор точек.


    1. DarkEld3r
      06.07.2016 12:42
      +1

      А разве на расте эта задача вообще решается?


      Да, из-за необходимости указывать трейты для параметров дженериков получается, что некий (обязательный) аналог концептов уже есть. И с передачей владения тоже всё неплохо сделано. Но если представить, что мы хотим решить задачу так как она сформулирована в статье, то что можем сделать?


    1. 0xd34df00d
      09.07.2016 22:49

      Невольно сравнивая с теми же вещами в Haskell, не могу не почувствовать разницу в возможностях. Серьёзно, в ряде важных случаев на тайпклассах отрицание не выражается (вроде «реализовать тайпкласс для всех вещей, не реализующих тайпкласс Bounded»), по крайней мере, жутчайших костылей, после которых тайпчекер сходит с ума.


      1. VoidEx
        10.07.2016 21:45

        реализовать тайпкласс для всех вещей, не реализующих тайпкласс Bounded

        А это как? Создал тип данных, импортнул ваш модуль — и инстанс тайпкласса есть, могу им воспользоваться в функции. Добавил в соседнем инстанс Bounded — и в нём инстанса вашего тайпкласса уже нет, но функцию мою, к примеру, можно звать?


        1. 0xd34df00d
          11.07.2016 04:48

          Добавил в соседнем инстанс Bounded — и в нём инстанса вашего тайпкласса уже нет, но функцию мою, к примеру, можно звать?

          Эм, почему? У вашей функции будет же сигнатура вроде ¬(Bounded a) => a -> foobar, разве нет?

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


          1. VoidEx
            11.07.2016 10:12

            У вашей функции будет же сигнатура вроде ¬(Bounded a) => a -> foobar, разве нет?

            Необязательно, может, просто MyType -> Blah.

            Это просто выглядит как-то императивно что ли, когда от порядка объявления зависит код. Грубо говоря, если запретить даже orphans, то всё будет достаточно строго — появился класс и вместе с ним все инстансы, появился тип — с ним все инстансы. Всё достаточно однозначно. Необходимость в orphan instances ещё можно понять, если авторы и класса, и типа, — сторонние люди, а инстанс вполне однозначен; а вот описанная ситуация, когда от добавления инстанса другой инстанс должен пропадать, создаёт устойчивое впечатление, что что-то тут не так :)


            1. 0xd34df00d
              12.07.2016 03:38

              Необязательно, может, просто MyType -> Blah

              А какое отношение тогда MyType имеет к Bounded? Я сходу не могу придумать, как оно у вас бы так получилось.

              Либо там всё совсем скрыто, и от наличия инстанса в текущем модуле ничего не сломается.

              То есть, я не вижу, где оно бы от порядка зависело.


              1. VoidEx
                13.07.2016 17:59

                Как в изначальном сценарии:

                А это как? Создал тип данных [MyType], импортнул ваш модуль — и инстанс [ваш, для всех, у кого нет инстанса Bounded] тайпкласса есть, могу им воспользоваться в функции [foo :: MyType -> Blah]. Добавил в соседнем инстанс Bounded — и в нём инстанса вашего тайпкласса уже нет, но функцию мою [foo], к примеру, можно звать?

                А если я выставлю набор функций, каждая из которых просто напросто дублирует функции вашего класса, но специализирована для MyType и соответственно реализована через позыв функций вашего have-no-Bounded-инстанса, то получится как бы обходной манёвр, несмотря на наличие в текущем scope инстанса Bounded для MyType, я буду использовать proxy-функции из соседнего модуля, где этого Bounded нету.

                Я правильно понял, что ваш инстанс не сработает для типа, у которого в scope есть также инстанс Bounded? Т.е. если другого инстанса нет, будет ошибка?

                Сравните, например, с Overlapped. Там есть один инстанс, его можно подменить другим (более специализированным), но, вроде как, нельзя убрать инстанс.

                Я не говорю, что с этим будут какие-то проблемы (хотя, наверное, могут и быть, но я не буду ручаться за конкретные сценарии), я о том, что это как-то нематематично что ли, как и Overlapped/Undecidable, в общем-то.


                1. 0xd34df00d
                  14.07.2016 05:37

                  А, я вас понял, наконец!

                  Функцию звать, получается, можно, да. Это всё ведь детали реализации. Более того, если я правильно вас понимаю, вместо костылей с дублированием API вы можете просто взять и написать newtype.

                  На самом деле в моей задаче Overlapped не подходил, хотя инстанс был и для Bounded-варианта. Я, к сожалению, напрочь забыл подробности, но мне хотелось тайпкласс LowerBound a с методом lower :: a, который был бы minBound для Bounded и -\infty для Real. Не помню, почему у меня Overlapped не хватало, и откуда вылезала необходимость в отрицании тайпкласса :(


  1. tower120
    06.07.2016 22:31

    В gcc 6.1 концепты уже есть из TS спецификации.
    Нужно добавить следующие флаги для компиляции:


    -fconcepts -std=c++1z

    Пример http://coliru.stacked-crooked.com/a/a59567cac9c7681d
    Документация http://en.cppreference.com/w/cpp/language/constraints


    1. izvolov
      06.07.2016 22:53
      +1

      Есть. Но необходимость в костылях на велосипедном приводе полностью отпадёт только тогда, когда это будет во всех компиляторах, и во всех них оно будет работать одинаково и включаться одними флагами компиляции.


      Всё-таки TS и стандарт — разные вещи.


      1. tower120
        07.07.2016 01:13
        -1

        Зачем вам во всех компиляторах? Вы же на каком то конкретном работаете, а не на всех сразу.


        1. izvolov
          07.07.2016 01:21
          +2

          Странный вопрос.


          Во-первых, допустим, я хочу писать переносимый код.
          Во-вторых, допустим, я хочу в бою использовать GCC, а для статического анализа — Clang.
          В-третьих, чем больше компиляторов с выкрученными на максимум предупреждениями скомпилируют код, тем больше я буду в нём уверен.


          Ну и т.д.


          1. tower120
            07.07.2016 01:32

            Просто интересно как у вас проект собирается что вы с такой легкостью компиляторы меняете.


            1. izvolov
              07.07.2016 01:34
              +2

              Обычный Cmake.


              ?\_(?)_/?


          1. 0xd34df00d
            09.07.2016 22:51

            Переносимый код для меня — это когда код не привязан к нестандартным особенностям конкретного компилятора.
            Ну, формально переносимый код может даже ничем не собираться, и у меня был опыт написания такого кода, потому что в gcc одни баги, в clang — другие :)

            А в бою, как по мне, clang уже генерит более оптимизированный код. На днях с автовекторизатором баловался — в моей задаче clang смог больше вещей раскрутить и более оптимизированный ассемблер выдать. Но это так, конечно же, YMMV.


            1. izvolov
              09.07.2016 23:52

              Переносимый код для меня — это когда код не привязан к нестандартным особенностям конкретного компилятора.

              Ну так код из публикации и не привязан ни к каким нестандартным особенностям.


              clang уже генерит более оптимизированный код

              В моих проектах гэцэцэ побыстрее.


              Хотя на простых испытательных примерах кланг действительно частенько генерирует более оптимальный ассемблерный код.


              YMMV

              Чё?


              1. 0xd34df00d
                10.07.2016 02:17

                Ну так код из публикации и не привязан ни к каким нестандартным особенностям.

                Да, но
                Зачем вам во всех компиляторах? Вы же на каком то конкретном работаете, а не на всех сразу.

                Если этот компилятор поддерживает стандарт на должном уровне, то и хорошо, разве нет?

                Хотя на простых испытательных примерах кланг действительно частенько генерирует более оптимальный ассемблерный код.

                Ну, не сказал бы, что у меня простые испытательные примеры. Циклы не самые тривиальные, дешугаринг сделать надо не самый тривиальный от всех этих итераторов, границы циклов известны не всегда, операции доступа памяти скрыты за operator() и шаблонными стратегиями лейаута матриц в памяти…
                Я был приятно удивлён.

                Чё?

                Your mileage may vary.