5
\$\begingroup\$

The Goal

Provide some wrappers to a C++11 template library's main entry point so that if the initial template parameter is not valid, the user is informed of exactly what is missing.

In the real library, each class Device is actually a template <class Projection> class SomeDevice {/* ... */ };, and each Projection class has about 10 static constexpr definitions used throughout. If a user has a bad Projection, I'd like to tell them this before hundreds of compiler errors fly across the screen using static_assert.

Wielding SFINAE is new to me, and in the research I became most attached to std::void_t. Since this is a C++11 project (by necessity, dependencies...):

Question 1: is it safe to just define void_t myself?

#include <iostream>  // question split into parts, copy
#include <type_traits>// paste top to bottom for MWE
///////////////////////////////////////////////////////
// SFINAE setup
//<<< Q1 (a) >>>
namespace internal { template< class... > using void_t = void; }

//<<< Q1 (b) >>> [would also be in `namespace internal`
// template<typename... Ts> struct make_void { typedef void type;};
// template<typename... Ts> using void_t = typename make_void<Ts...>::type;

// primary template handles types that have no nested ::my_def member:
template< class, class = internal::void_t<> >
struct has_my_def : std::false_type { };

// specialization recognizes types that do have a nested ::my_def member:
template< class T >
struct has_my_def<T, internal::void_t<typename T::my_def>> : std::true_type { };

Though many other options exist, this is my favorite. The definition of std::void_t both in the link, as well as checking the actual type_traits headers seems foolproof. Is there any danger in using (a)? The linked article mentioned (b), but the actual reason for it was well beyond my understanding.

Question 2: choosing how to design the wrapper -- struct or function?

Regardless of the actual definition of has_my_def (the void_t approach, typedef char yes[1]; typedef char no[2] ...), is there any reason to use a wrapper struct over an overloaded (right term?) function?

  • 2(a) -- Struct approach:

    namespace internal {
        template <class Device, bool valid>
        struct proxy {
            static int execute() {
                // if straight `false`, will always fail. I am
                // worried that some compilers might see "oh <true>
                // is specialized, so `valid` must be `false`"
                //            vvvvv
                static_assert(valid, "You have been asserted.");
                return 1;
            }
        };
    
        template <class Device>
        struct proxy<Device, true> {
            static int execute() {
                std::cout << "Do some stuff" << std::endl;
                return 0;
            }
        };
    }
    
    // this would be the "main entry point"
    template <class Device>
    int execute() {
        return internal::proxy<Device, has_my_def<Device>::value>::execute();
    }
    
  • 2(b) -- Function approach

    template <class Device>
    typename std::enable_if< !has_my_def<Device>::value, int>::type
    alt_execute() {
        // but if `valid` above got inferred, would the same not
        // be so for  vvvvvvvvvvvvvvvvvvvvvvvvv ?
        static_assert(has_my_def<Device>::value, "You have been asserted.");
        return 1;
    }
    
    template <class Device>
    typename std::enable_if< has_my_def<Device>::value, int>::type
    alt_execute() {
        std::cout << "Work gets done." << std::endl;
        return 0;
    }
    

Simple main if desired

struct Good { using my_def = float; };
struct Bad {};

int main(int argc, const char **argv) {
    // sanity checks
    std::cout << std::boolalpha
              << "Good: " << has_my_def<Good>::value << std::endl
              << "Bad:  " << has_my_def<Bad>::value  << std::endl;

    // good
    int g1 = execute<Good>();
    int g2 = alt_execute<Good>();

    int b1 = 0, b2 = 0;
    // fail :) uncomment one or the other, if both
    // uncommented, only b1 gets compiled (goal)
    // b1 = execute<Bad>();
    b2 = alt_execute<Bad>();

    return g1 + g2 + b1 + b2;// -Wno-unused-variable ;)
}

The "dilemma"

I don't really know which approach to use, or if I am missing something obvious. I tend to want to lean toward the function approach because, since I need to check about 10 more nested types of my_def, I was thinking it would be possible to write one 10 parameter function where the static assert just says "here are all the things you need", instead of writing a macro to generate 10 structs.

Any thoughts are appreciated, I think understanding best practices here is really important!

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.