Search code examples
c++language-lawyernoexcept

Need for std::decay in noexcept operator?


The following code requires using std::decay inside noexcept operator in gcc, but not in clang.

template<typename... Ts>
class B;

template<typename T>
class B<T> {
    T t;
public:
    template<typename U>
    constexpr B(U&& t)
    // without decay - strange behavior in gcc, see main below  <===
    noexcept(noexcept(T{std::forward<U>(t)}))
        // if adding decay - all cases in main are ok with both gcc and clang
        // noexcept(noexcept(std::decay_t<T>{std::forward<U>(t)}))
    : t(std::forward<U>(t)) {}
};

template<typename T, typename... Ts>
class B<T, Ts...>: protected B<Ts...> {
public:
    template<typename U, typename... Us>
    constexpr B(U&& t, Us&&... ts)
        : B<Ts...>{std::forward<Us>(ts)...} {}
};

template<typename... Ts>
constexpr auto create(Ts&&... ts) {
    return B<Ts...>{std::forward<Ts>(ts)...};
}

template<typename... Ts>
B(Ts...) -> B<Ts...>;

main

int main() {
    // ok in both gcc and clang:
    // [1] the "hello" parameter is not last
    auto b1 = create("hello", 1); 
    auto b2 = create(1, "hello", 5);
    // [2] passing it directly to the ctor of B
    B b3(1, "hello");

    // fails with gcc when the noexcept doesn't use decay
    // but only if "hello" is the last argument and passed via a function
    auto b4 = create(1, "hello");
    auto b5 = create("hello");
}

The compilation error by gcc is:

<source>:13:40: error: invalid conversion from 'const char*' to 'char' 
         [-fpermissive]

   13 |     noexcept(noexcept(T{std::forward<U>(t)}))
      |                         ~~~~~~~~~~~~~~~^~~
      |                                        |
      |                                        const char*

Code: https://godbolt.org/z/s7rf64

Any idea for this strange behavior? Is it a gcc bug? or is std::decay indeed required?


Solution

  • Following the remark by @Marek R it seems to be a bug in gcc.

    A bug was opened on gcc.

    The following case also fails with gcc and not with clang:

    template<typename T, typename U>
    auto convert(U&& t) {
        // fails on gcc:
        return T{std::forward<U>(t)};
        // works ok if it is not brace initialization:
        // return T(std::forward<U>(t));
    }
    
    template<std::size_t INDEX, typename T, typename U, typename... Us>
    auto convert(U&& t, Us&&... ts) {
        if constexpr(INDEX == 0) {
            return convert<U>(t);
            // works well if we add decay
            // return convert<std::decay_t<U>>(t);
        }
        else {
            return convert<INDEX-1, T>(ts...);
        }
    }
    
    int main() {
        // fails with gcc
        auto p = convert<1, const char*>(1, "hello");
    }
    

    Code: https://godbolt.org/z/jqxbfj