3

I'm programming some classes into which I inject dependencies via class template parameters.

I some cases, the dependency classes have, or can have, static constexpr members that declare some of their specific characteristics. In the case of the example below, classes implementing a Renderer "concept" can define a static constexpr bool variable y_axis_bottom_up to indicate that they expect vertical coordinates that grow in upward direction.

I could of course make it a requirement that all Renderer implementations provide this boolean member, but I'd prefer to define a default value in the query expression.

Because I need to use that information to parameterize further templates, that query expression must be constexpr.

I thought I had the solution when I found this stackoverflow answer, but when I tried to apply it to my needs, I failed.

Here's the - non-working - minimal test code I came up with. The second output line should say "1", but both outputs are "0".

Any help would be appreciated.

#include <iostream>
#include <type_traits>

template<class Config, typename = void>
struct vertical_axis_bottom_up
{
    static constexpr bool value = false;
};

template<class Config> 
struct vertical_axis_bottom_up<Config, decltype(Config::Renderer::y_axis_bottom_up)>
{
    static constexpr bool value = Config::Renderer::y_axis_bottom_up;
};

struct Dummy_renderer {};

struct Config1
{
    using Renderer = Dummy_renderer;
};

struct Dummy_renderer_2
{
    static constexpr bool y_axis_bottom_up = true;
};

struct Config2
{
    using Renderer = Dummy_renderer_2;
};

int main(int /*argc*/, char * /*argv*/[])
{
    std::cout << "Default value for Config::Renderer::y_axis_bottom_up : " << vertical_axis_bottom_up<Config1>::value << std::endl;
    std::cout << "Explicit value for Config::Renderer::y_axis_bottom_up: " << vertical_axis_bottom_up<Config2>::value << std::endl;

    std::cout << "Press RETURN to terminate" << std::endl;
    char ch; std::cin >> std::noskipws >> ch;

    return -1;
}
Community
  • 1
  • 1
JPNotADragon
  • 2,050
  • 2
  • 25
  • 31

1 Answers1

3

This is done extensively in the standard library, allocator_traits is a good example. Here's the basic idea:

#include <iostream>
#include <type_traits>


template <class T, class = void>
struct MyTrait
{
    static constexpr bool foo = false;
};

template <class T>
struct MyTrait<T, std::enable_if_t<std::is_same<const bool, decltype(T::foo)>::value>>
{
    static constexpr bool foo = T::foo;
};

struct Class1 {

};

struct Class2 {
 static constexpr bool foo = true;   
};


int main()
{
    std::cerr << MyTrait<Class1>::foo;
    std::cerr << MyTrait<Class2>::foo;
}

Basically, this is using a trick very similar to the void_t trick (google if you are not familiar). If the templated class provides a member of the correct type, then it picks up that member. If not, it falls back to false. So when you access the foo trait through MyTrait, you are guaranteed for it be present.

Working example: http://coliru.stacked-crooked.com/a/d73f3674d0c53e53

Nir Friedman
  • 17,108
  • 2
  • 44
  • 72