Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Understanding more about type_traits

Setup

I asked a question yesterday about template method overloading and resolving issues using type traits. I received some excellent answers, and they led me to a solution. And that solution led me to more reading.

I landed on a page at Fluent CPP -- https://www.fluentcpp.com/2018/05/18/make-sfinae-pretty-2-hidden-beauty-sfinae/ that was interesting, and then I listened to the Stephen Dewhurst talk that Mr. Boccara references. It was all fascinating.

I'm now trying to understand a little more. In the answers yesterday, I was given this solution:

     template< class Function, class... Args,
              std::enable_if_t<std::is_invocable_v<Function, Args...>, std::nullptr_t> = nullptr>
     explicit MyClass( const std::string & theName, Function&& f, Args&&... args )
        : name(theName)
     {
        runner(f, args...);
     }

Alternative Answer

After reading the CPP Fluent post and watching the talk, I came to this final solution:

   template< class Function, class... Args>
   using IsInvocable = std::enable_if_t < std::is_invocable_v<Function, Args...> >;

    template< class Function, class... Args, typename = IsInvocable<Function, Args...> >
    explicit ThreadHandle( const std::string & name, Function && f, Args &&... args ) {
        startWithName(name, f, args...);
    }

The first bit just moves some of the syntax into a common include file, but overall, this is simpler. I think this is clean and requires little explanation, even for someone unfamiliar with using type traits.

The Question

What I'm wondering is this. All three answers I received used a more complex form of enable_if_t like this:

std::enable_if_t<std::is_invocable_v<Function, Args...>, std::nullptr_t> = nullptr>

And I'm not sure why they would do that if I can do this instead:

std::enable_if_t< std::is_invocable_v < Function, Args... > >

Are there implications? Or is this simply a matter of the more complex one is C++11, and now C++ 14 and 17 allows a simpler form? Perhaps the people responding were simply helping me out by showing me the complete form.

To add to my confusion, one of the answers did this:

std::enable_if_t<!std::is_convertible_v<Function, std::string>, bool> = true>

And another one did this:

std::enable_if_t<std::is_invocable_v<Function, Args...>, int> = 0>

I don't really understand these implications, either.

Any help getting over the hurdle would be great. I imagine there will be cases I'll want the more complicated versions, so understanding it better would be good.

like image 868
Joseph Larson Avatar asked Dec 14 '21 14:12

Joseph Larson


People also ask

What is Type_traits?

Type traits are a clever technique used in C++ template metaprogramming that gives you the ability to inspect and transform the properties of types.

What is Typename C++?

Use the keyword typename if you have a qualified name that refers to a type and depends on a template parameter. Only use the keyword typename in template declarations and definitions.

What is the use of type traits in C++?

In C++, we can think about type traits as properties of a type. The <type_traits> header was an addition introduced by C++11. Type traits can be used in template metaprogramming to inspect or even to modify the properties of a type.

How can type traits alter types?

Now let’s have a look at how type traits can alter types. There are templates shipped in the <type_traits> header that can add or remove const and/or volatile specifiers from a given type Let’s see three examples. With std::add_const / std::remove_const you can add/remove the topmost const of a type:

Do you have type a personality traits?

Many Type A people try to show dominance in business and personal interactions, disregarding the wishes and needs of others in favor of their own. Years of Type A behavior and stress can prompt physical characteristics and changes, including:

What are type traits and type transformations?

Type traits: Classes to obtain characteristics of types in the form of compile-time constant values. Type transformations: Classes to obtain new types by applying specific transformations to existing types. A basic trait for types is the categories in which they can be classified. This is a chart on how these categories overlap:


Video Answer


1 Answers

Vocabulary

// template-head
template<typename T = T{}>
//       ^^^^^^^^^^   ^^^- default template-argument
//           \ type template-parameter

// template-head
template<int i = 0>
//       ^^^^^   ^- default template-argument
//           \ non-type template-parameter  

Default template arguments are not part of a function template's type

Default template arguments are not part of a function template's type, meaning you cannot use the following approach:

// BAD: trying to define to SFINAE-mutually exclusive overloads.
template<typename T, typename = std::enable_if_t<some_predicate_v<T>>>
void f(T) {}

template<typename T, typename = std::enable_if_t<!some_predicate_v<T>>>
void f(T) {}

as these define the same function; see e.g.

  • Default template argument when using std::enable_if as templ. param.: why OK with two template functions that differ only in the enable_if parameter?

for details.

... whereas different types of a non-type template parameters can be used as an alternative

Thus, the approach above is typically used when you do not do overloading of otherwise identical functions, whereas the other family is used when you need to differentiate overloads.

// Variation A.
template<typename T,
         // non-type template parameter of type void*,
         // defaulted to nullptr 
         std::enable_if_t<some_predicate_v<T>>* = nullptr>
void f(T) {}

// OK: not the same function.
template<typename T,
         std::enable_if_t<!some_predicate_v<T>>* = nullptr>
void f(T) {}

// Variation B.
template<typename T,
         // non-type template parameter of type bool,
         // defaulted to true or false 
         std::enable_if_t<some_predicate_v<T>, bool> = true>
void f(T) {}

// OK: not the same function.
template<typename T,
         std::enable_if_t<!some_predicate_v<T>, bool> = true>
void f(T) {}

// Variation C.
template<typename T,
         // non-type template parameter of type int,
         // defaulted to 0
         std::enable_if_t<some_predicate_v<T>, int> = 0>
void f(T) {}

// OK not the same function.
template<typename T,
         std::enable_if_t<!some_predicate_v<T>, int> = 0>
void f(T) {}

// Variation D (uncommon/noisy).
template<typename T,
         // non-type template parameter of type std::nullptr_t,
         // defaulted to nullptr
         std::enable_if_t<some_predicate_v<T>, std::nullptr_t> = nullptr>
void f(T) {}

// OK: not the same function.
template<typename T,
         std::enable_if_t<!some_predicate_v<T>, std::nullptr_t> = nullptr>
void f(T) {}

Note that for variation A we leverage the fact that the 2nd template parameter of std::enable_if (alias via the _t alias template) is defaulted to void.

like image 180
dfrib Avatar answered Oct 25 '22 13:10

dfrib