Search code examples
c++templatestemplate-meta-programmingconditional-compilation

Is it possible to conditionally disable a global function definition using template metaprogramming?


Say I have a simple nullary template function templated on a single parameter, with two specializations, one for unsigned long, and one for size_t (contents not important):

template<typename T> T f(void);
template<> unsigned long f<unsigned long>(void) { return 1; }
template<> size_t f<size_t>(void) { return 2; }

My understanding is that the exact definition of the type size_t is platform-dependent, and so it may or may not be equivalent to unsigned long. On my current platform (Cygwin g++ 5.2.0 on Windows 10 64-bit compiling with -std=gnu++1y) these two types appear to be equivalent, so the above code fails to compile:

../test.cpp:51:19: error: redefinition of ‘T f() [with T = long unsigned int]’
 template<> size_t f<size_t>(void) { return 2; }
                   ^
../test.cpp:50:26: note: ‘T f() [with T = long unsigned int]’ previously declared here
 template<> unsigned long f<unsigned long>(void) { return 1; }
                          ^

From my thinking, this problem could be solved by simply disabling the size_t function definition, since any code that attempted to call f<size_t>() would automatically resolve to f<unsigned long>(). But the function should be enabled for platforms that define size_t to be different from unsigned long.

I've read a little bit about template metaprogramming and SFINAE, and I've been playing with things like this:

std::enable_if<(sizeof(size_t) > sizeof(unsigned long))>::type 

But I'm unsure how to use such a fragment to disable a global function definition, if that's possible at all.

So, is there any way to conditionally disable a global function definition using template metaprogramming? Or, more generally, am I on the right track, or heading down a wrong path?


Solution

  • This works in any case, but it's a bit tedious and doesn't scale that well for a larger number of specializations:

    template<typename T
           , std::enable_if_t<!std::is_same<T, unsigned long>::value
                           && !std::is_same<T, size_t>::value>* = nullptr>
    T f() { return 1; }
    
    template<typename T
           , std::enable_if_t<std::is_same<T, unsigned long>::value>* = nullptr>
    T f() { return 2; }
    
    template<typename T
           , std::enable_if_t<std::is_same<T, size_t>::value
                          && !std::is_same<T, unsigned long>::value>* = nullptr>
    T f() { return 3; }
    

    The idea: don't specialize but overload, and enable the overloads only if the signature is appropriate (while disabling the others at the same time).

    Further, in order to make it better maintainable, one should outsource the logical checks into another suitable class.


    DEMO:

    int main()
    {
        std::cout<<f<int>()<<std::endl;
        std::cout<<f<unsigned long>()<<std::endl;
        std::cout<<f<size_t>()<<std::endl;
        std::cout<<f<unsigned long long>()<<std::endl;
    }
    

    It prints:

    1
    2
    2
    1
    

    So it seems "size_t == unsigned long" on coliru.