Reputation: 2066
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:
DT
so that it is available in the noexcept
specifier?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
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
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
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