Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Order of preference for SFINAE template?

Tags:

c++

sfinae

I'm trying to implement templated structs that take a version parameter.

Here's a simplified example:

template<int Version, class Enable = void>
struct Foo
{
};

template<int Version, class Enable = void>
struct Bar
{
};

/* Base Version of Foo */
template<int Version>
struct Foo<Version, typename enable_if<(Version > 0)>::type>
{
    int i;
};

/* Base Version of Bar */
template<int Version>
struct Bar<Version, typename enable_if<(Version > 0)>::type>
{
    Foo<Version> foo;
    float f;
};

/* Version 2 of Bar */
template<int Version>
struct Bar<Version, typename enable_if<(Version >= 2)>::type>
{
    Foo<Version> foo;
    float f;
    int extraParam;
};

With this approach, there is ambiguity when I use "Bar<2>", because 2 satisfies both the base version's condition (Version > 0) and the version 2 condition (Version >= 2).

I could change the base to require "Version > 0 && Version < 2", but I was hoping to avoid having to do that everywhere. Is there a better way to tell the compiler "Use the highest matching version" for a given template ?

like image 893
Stephen Avatar asked Jan 20 '26 07:01

Stephen


1 Answers

Using the example link provided by dyp, I was able to solve the problem with recursion.

#include <iostream>
#include <type_traits>

using namespace std;

template<int Version>
struct Foo
{
    constexpr static bool is_valid = false;
};

template<int Version>
struct Bar
{
    constexpr static bool is_valid = false;
};

struct ValidVersion { constexpr static bool is_valid = true; };

/* Base Version of Foo */
template<>
struct Foo<0> : ValidVersion
{
    int i;
};

/* Base Version of Bar */
template<>
struct Bar<0> : ValidVersion
{
    Foo<0> foo;
    float f;
};

/* Version 2 of Bar */
template<>
struct Bar<2> : ValidVersion
{
    Foo<2> foo;
    float f;
    int extraParam;
    int extraParam2;
};

template<template<int V> class _Tp, int Version>
struct VersionSelectorImpl
{
    template<class T>
    struct wrap { using type = T; };
    using type = typename std::conditional<_Tp<Version>::is_valid, wrap<_Tp<Version>>, VersionSelectorImpl<_Tp, Version-1>>::type::type;
};

template<template<int V> class _Tp, int Version>
using VersionSelector = typename VersionSelectorImpl<_Tp, Version>::type;

int main() {
    cout << "sizeof(<Bar, 1>): " << sizeof(VersionSelector<Bar, 1>) << '\n';
    cout << "sizeof(<Bar, 2>): " << sizeof(VersionSelector<Bar, 2>) << '\n';
}

Output:

sizeof(<Bar, 1>): 12
sizeof(<Bar, 2>): 16

like image 174
Stephen Avatar answered Jan 22 '26 20:01

Stephen