Странное поведение спецификатора noexcept в C ++ 14 - PullRequest
7 голосов
/ 13 февраля 2020

Я обнаружил странное поведение оператора noexcept в C ++ 14. Следующий код хорошо компилируется как g cc, так и clang (с опцией --std = c ++ 14).

// test.cpp
#include <iostream>
#include <type_traits>

#if 1
#define TESTREF(X) X&&
#else
#define TESTREF(X) X const&
#endif

template <class F, class... Args>
struct is_noexcept_callable
    : public std::conditional_t<noexcept(std::declval<F>()(std::declval<Args>()...)), std::true_type, std::false_type> {};

template <
    class F,
    std::enable_if_t<is_noexcept_callable<F,int>::value,int> = 0
    >
int evalInt(int x, TESTREF(F) f) noexcept
{
    return static_cast<int>(f(x));
}

template <
    class F,
    std::enable_if_t<!is_noexcept_callable<F,int>::value,int> = 0
    >
int evalInt(int x, TESTREF(F) f)
{
    return static_cast<int>(f(x));
}

int id(int x) noexcept { return x; }
int thrower(int x) { throw(0); }

int main(int argc, char* argv[])
{
    std::cout << std::boolalpha
              << noexcept(evalInt(1,id))
              << std::endl;
    std::cout << std::boolalpha
              << is_noexcept_callable<decltype(thrower), int>::value
              << std::endl;
}

Выполняя программу, я получил разные результаты в зависимости от компиляторов:

$ g++ --std=c++14 test.cpp
$ ./a.out
true
false
$ clang++ --std=c++14 test.cpp
$ ./a.out
false
false

Я не уверен, что является правильным в соответствии со стандартом.

Более странно, если я изменю 5-ю строку в приведенном выше коде на #if 0, тогда g cc скомпилирует код в другую другую программу:

$ ./a.out
true
true

Как видите, второе значение изменилось. Однако это зависит только от спецификации noexcept функции thrower, которую макрос не касается. Есть ли разумное объяснение этому или это просто ошибка?


Редактировать

Результат получается с G CC 7.4.0 и clang 6.0.0 в репозитории пакетов Ubuntu 18.04 (64bit).

1 Ответ

3 голосов
/ 13 февраля 2020

Я могу воспроизвести эту ошибку только в G CC до версии 8. Различие в поведении связано с тем, что спецификатор noexcept является частью типа функции в версии C ++ 14 для G CC 7 (но не Clang's), хотя это особенность C ++ 17. Это можно увидеть, если добавить частичные специализации is_noexcept_callable:

template <class... Args>
struct is_noexcept_callable<int(&)(int), Args...>
    : public std::false_type {};

template <class... Args>
struct is_noexcept_callable<int(int), Args...>
    : public std::false_type {};

Это внезапно приводит к двум false s : G CC сохраняет атрибут noexcept в функции типов, но в явном виде игнорирует их во время вывода аргумента шаблона , так что вышеупомянутые специализации выбраны, несмотря на сообщения об ошибках, показывающие noexcept, если мы удалим определения:

prog.cc:30:5: note:   template argument deduction/substitution failed:
prog.cc:28:22: error: incomplete type 'is_noexcept_callable<int (&)(int) noexcept, int>' used in nested name specifier

Почему влияет ли определение TESTREF на is_noexcept_callable?

Вторая часть вашего вопроса более тонкая. Здесь проблема в том, что is_noexcept_callable уже создан с соответствующим типом int(int) [noexcept], прежде чем использовать его в main, но к нему не прикреплено ничего, кроме того, что результат is_noexcept_callable<int(int), int>::value фиксируется в true.

decltype(id) - это int(int) [noexcept], где [noexcept] - мое обозначение переходной спецификации исключений express G CC, прикрепленной к типу функции. Таким образом, evalInt(1,id) вызывает создание экземпляров

  • is_noexcept_callable<F,int>, где F = int(&)(int) [noexcept] при TESTREF = X&& и
  • F = int(int) [noexcept] при TESTREF = X const&

Таким образом, когда вы отключаете первую ветвь своей директивы if, is_noexcept_callable<int(int),int>::value == true удерживается после обработки noexcept(evalInt(1,id)), потому что id не исключает, и это распространяется по цепочке создания .

Следовательно, следующее печатает две ложные:

int main(int argc, char* argv[])
{
    std::cout << std::boolalpha
              << noexcept(evalInt(1,thrower))
              << std::endl;
    std::cout << std::boolalpha
              << is_noexcept_callable<decltype(thrower), int>::value
              << std::endl;
}

Демонстрация: https://wandbox.org/permlink/YXDYfXwtEwMQkryD

...