1

Take, for example, the following:

#include <iostream>
#include <type_traits>
#include <utility>


struct Bar
{
    Bar() = default;
    Bar(Bar const&) noexcept(false) = default;
    Bar(Bar&&) noexcept(true) = default;
};

struct Baz
{
    Baz() = default;
    Baz(Baz const&) noexcept(true) = default;
    Baz(Baz&&) noexcept(false) = default;
};

template<typename T>
class Foo
{
    template<typename U, typename V>
    using enable_if_same = std::enable_if<std::is_same<typename std::remove_reference<U>::type, V>::value, U>;

public:
    template<typename U>
    Foo(typename enable_if_same<U, T>::type&& val) // noexcept( ? )
        : _val(std::forward<U>(val))
    {
    }

protected:
    T _val;
};


int main(void)
{
    std::cout << "Is the Bar copy constructor noexcept? " << std::is_nothrow_copy_constructible<Bar>::value << "\n";
    std::cout << "Is the Bar move constructor noexcept? " << std::is_nothrow_move_constructible<Bar>::value << "\n";
    std::cout << "Is the Foo<Bar> copy constructor noexcept? " << std::is_nothrow_copy_constructible<Foo<Bar>>::value << "\n";
    std::cout << "Is the Foo<Bar> move constructor noexcept? " << std::is_nothrow_move_constructible<Foo<Bar>>::value << "\n";

    std::cout << "\n";

    std::cout << "Is the Baz copy constructor noexcept? " << std::is_nothrow_copy_constructible<Baz>::value << "\n";
    std::cout << "Is the Baz move constructor noexcept? " << std::is_nothrow_move_constructible<Baz>::value << "\n";
    std::cout << "Is the Foo<Baz> copy constructor noexcept? " << std::is_nothrow_copy_constructible<Foo<Baz>>::value << "\n";
    std::cout << "Is the Foo<Baz> move constructor noexcept? " << std::is_nothrow_move_constructible<Foo<Baz>>::value << "\n";

    return 0;
}

Compiling and running the above code produces the expected output:

Is the Bar copy constructor noexcept? 0
Is the Bar move constructor noexcept? 1
Is the Foo<Bar> copy constructor noexcept? 0
Is the Foo<Bar> move constructor noexcept? 1

Is the Baz copy constructor noexcept? 1
Is the Baz move constructor noexcept? 0
Is the Foo<Baz> copy constructor noexcept? 1
Is the Foo<Baz> move constructor noexcept? 0

I'm interested to know, however, is it possible to explicitly specify the noexcept-ness of the Foo(U&&) constructor, taking a forwarding reference (i.e. what do I need to replace ? with in the commented-out noexcept specifier above)?

jinscoe123
  • 1,467
  • 14
  • 24

1 Answers1

2

You can do something like this:

template<typename U, typename = 
    std::enable_if_t<std::is_same_v<std::remove_reference_t<U>, T>>>
Foo(U&& val) noexcept(noexcept(T(std::forward<U>(val))))
    : _val(std::forward<U>(val))
{}

The inner noexcept is a noexcept operator, the outer one is a noexcept specifier.

Demo


I had to change the way SFINAE is used, because in your constructor

template<typename U>
Foo(typename enable_if_same<U, T>::type&& val) 
    : _val(std::forward<U>(val))
{}

the type U cannot be deduced (and even if it could, ...::type&& won't be a forwarding reference).

Evg
  • 25,259
  • 5
  • 41
  • 83
  • Thanks! This seems to do the trick. Also, what do you mean when you say that the type U cannot be deduced in my constructor? The example I gave above compiles just fine for me under g++ 10.1.0. – jinscoe123 Jul 29 '20 at 07:10
  • @jinscoe123, in your example that constructor is never called. Try `Foo(Bar{});` and you'll see an error: `couldn't infer template argument 'U'`. – Evg Jul 29 '20 at 07:11
  • Ah, yes, you're right. Thanks for pointing that out to me! – jinscoe123 Jul 29 '20 at 07:17
  • Also, I just noticed that `noexcept(std::is_nothrow_constructible_v)` also seems to work as well. Is there any difference between this and your solution above? Or do they both accomplish the same thing? – jinscoe123 Jul 29 '20 at 07:18
  • @jinscoe123, they are the same (if `_val` can actually be constructed from `std::forward(val)`). – Evg Jul 29 '20 at 07:27