marack
marack

Reputation: 2066

Using default template parameters to introduce derived type

I have a template in which I need to use a type which derived from the user supplied type T within the noexcept specifier multiple times. In the example below, I need to used the decayed type of T multiple times. The naive approach is:

// in this case 
template <typename T>
void foo(T&& t) noexcept(   noexcept(typename std::decay<T>::type(std::forward<T>(t)))
                         && noexcept(std::declval<typename std::decay<T>::type>() = std::forward<T>(t)))
{
  typedef typename std::decay<T>::type DT;
  /* ... */
}

The need to repeat typename std::decay<T>::type so often seems overly verbose. I could replace the whole expression with the DT type, but unfortunately this is only available in the function body.

The easiest way to introduce DT before the function body I can think of is this:

template <typename T, typename DT = typename std::decay<T>::type>
void foo(T&& t) noexcept(   noexcept(DT(std::forward<T>(t)))
                         && noexcept(std::declval<DT>() = std::forward<T>(t)))
{
  /* DT also available here... */
}

It feels like the wrong thing to add another template parameter just for this purpose though. So, my questions:

EDIT: It occurs to me that the real problem isn't so much gaining access to DT, but decreasing the complexity of the noexcept expression entirely (as it is used on many functions). I've added an answer below that creates a custom trait for this purpose.

Upvotes: 0

Views: 144

Answers (3)

marack
marack

Reputation: 2066

I just thought of a more workable solution that the one I had previously accepted. Instead of creating a wrapper template function for every time I have a complex noexcept expresion, I can introduce a custom trait. That way I can easily reuse the noexcept expression on multiple functions rather than write a wrapper for every function:

// complex trait only has to be written once
template <typename T>
struct my_noexcept_trait : std::integral_constant<bool, 
       noexcept(typename std::decay<T>::type(std::forward<T>(t)))
    && noexcept(std::declval<typename std::decay<T>::type>() = std::forward<T>(t))>
{ };

// but can be used simply in many functions
template <typename T>
void foo(T&& t) noexcept(my_noexcpt_trait<T>::value)
{ }

template <typename T>
void bar(T&& t) noexcept(my_noexcept_trait<T>::value)
{ }

Even if the trait is only used once, it's no worse than writing a wrapper template function, and if it is used multiple times then the complex expression is saved for each reuse.

Upvotes: 1

Daniel Frey
Daniel Frey

Reputation: 56903

I don't feel I can answer the second question of whether or not it is considered bad practice, as I think those are decisions on a case-by-case base. What works for you might not work for someone else.

What you should know about are the alternatives. Adding another default template parameter changes the API and opens up a potential for errors if the caller thinks he is required to provide both template parameters explicitly. This is unlikely and AFAICS usually not a big concern. The API-modification OTOH might be a problem.

In that case you might consider turning foo into a forwarder to foo_impl and only foo_impl has the second defaulted template parameter. That usually helps to keep the API clean. If you move foo_impl to an anonymous namespace (or into the private second if this is inside a class) the compiler might or might not inline it and even the generated code looks like your move verbose and explicit version.

namespace
{
    template <typename T, typename DT = typename std::decay<T>::type>
    void foo_impl(T&& t) noexcept(   noexcept(DT(std::forward<T>(t)))
                                  && noexcept(std::declval<DT>() = std::forward<T>(t)))
    {
       /* DT also available here... */
    }
}

// clean API
template <typename T>
void foo(T&& t) noexcept(noexcept(foo_impl(std::forward<T>(t))))
{
    return foo_impl(std::forward<T>(t));
}

Upvotes: 3

Tobias
Tobias

Reputation: 5198

The main difference between the two versions is that DT becomes part of the interface (template arguments) in the second version. If this function belongs to an api-interface and DT is not specified in the design you should not use the second version even if the alternative means some more code. But maybe you could write a wrapper instead in your implementation details.

Upvotes: 1

Related Questions