是否可以使用模板元编程有条件地禁用全局函数定义?

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

假设我有一个简单的 nullary 模板函数,它以单个参数为模板,有两个特化,一个用于 unsigned long,一个用于 size_t(内容不重要):

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

我的理解是 size_t 类型的确切定义是平台相关的,因此它可能等同于也可能不等同于 unsigned long。在我当前的平台上(Cygwin g++ 5.2.0 on Windows 10 64-bit compiling with -std=gnu++1y)这两种类型似乎是等价的,所以上面的代码编译失败:

../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; }
                          ^

根据我的想法,这个问题可以通过简单地禁用 size_t 函数定义来解决,因为任何试图调用 f<size_t>() 的代码都会自动解析为 f<unsigned long>()。但是对于定义 size_t 不同于 unsigned long.

的平台,应该启用该功能

我已经阅读了一些关于模板元编程和 SFINAE 的内容,并且我一直在玩这样的东西:

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

但我不确定如何使用这样的片段来禁用全局函数定义,如果可能的话。

那么,有什么方法可以使用模板元编程有条件地禁用全局函数定义吗?或者,更一般地说,我是走在正确的轨道上,还是走错了路?

这在任何情况下都有效,但它有点乏味并且不能很好地扩展到更多的专业化:

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; }

想法:不要特化而是重载,并且只有在签名合适时才启用重载(同时禁用其他的)。

此外,为了更好地维护它,应该将逻辑检查外包给另一个合适的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;
}

它打印:

1
2
2
1

所以在 coliru 上似乎是“size_t == unsigned long”。

根据我的经验:不能直接使用全局函数(在键入时阅读 davidhigh 的回答:好的,它有效,但正如他所说,它不能很好地扩展)。 SFINAE 仅在解析模板参数时出现 "error" 时才有效。由于 C++ 只允许完全特化函数模板,因此在编译器尝试编译特化时没有 "resolving"。

但是使用 类 编译器允许部分特化,你可以做这样的事情,它的好处是你只需要 size_t 的 SFINAE 表达式(在这里使用 mySize因为我可以改变它):

#include <iostream>
#include <type_traits>
using namespace std;

typedef unsigned int mySize;

//default
template <class P, class dummy = void>
class T{
    public:
   static P f(){return 0;}
};

//int
template <>
class T<int,void> {
    public:
   static int f(){return 1;}
};

//unsigned long
template <>
class T<unsigned long, void> {
    public:
   static unsigned long f(){return 2;}
};

template <class P>
class T<P, typename enable_if<is_same<P, mySize>::value && !is_same<P, unsigned long>::value, void>::type> {
    public:
   static P f(){return 3;}
};

int main() {
    cout << T<int>::f() << endl;
    cout << T<unsigned long>::f() << endl;
    cout << T<mySize>::f() << endl;
    return 0;
}

输出 typedef unsigned long mySize;:

1
2
2

使用任何其他 typedef 的输出(好吧,显然不是 int):

1
2
3

Try it online

这是一种有点奇怪但相当容易使用的方法:

//using MyType = unsigned int;
using MyType = unsigned long;

unsigned long f2(MyType *,int) { return 1; }
size_t        f2(size_t *,...) { return 2; }

template <typename T>
auto f() -> decltype(f2(static_cast<T*>(0),0)) {
    T* p = 0;
    return f2(p,0);
}

int main()
{
    std::cout << f<MyType>() << "\n";
    std::cout << f<size_t>() << "\n";
}

这里的想法是,您可以为 size_t 情况创建一个独特的功能,这不是首选,但如果没有其他选择,则会使用。如果 size_tMyType 相同,则使用 MyType 重载,否则将使用 size_t 重载。

f 调用 f2 并使用带有 decltype 的尾随 return 类型,以便如果特定类型 T 不存在 f2,那么f也不存在。

使用此技术,您还可以轻松地为其他类型添加重载。

我接受了@davidhigh 的回答,因为我认为这是最适合我所问问题的解决方案,但是,在我的实际代码中,我使用了不同的解决方案,以防万一它对其他人有帮助,我'我会在这里描述它。

我的解决方案基于@immibis 的评论,不幸的是,该评论已被删除。有点像 "Can't this be done easily using the preprocessor?" 我意识到 climits 中的 C *_MAX 宏实际上可以使用,而且解决方案非常简单。感谢@immibis!

我对所有可能导致冲突的类型应用了预处理器防护,包括有符号和无符号变体。这包括 size_tuintmax_tssize_tptrdiff_tintmax_t

此外,正如@tbleher 在他的评论中指出的那样,有时相同大小的标称类型可能是不同的真实类型,所讨论的示例是 unsigned longunsigned long long。事实上,在我当前的系统 sizeof(unsigned long) == sizeof(unsigned long long) == 8 上,签名变体也是如此。虽然大小相同,但被认为是不同的真实类型,不会冲突。

我的方法是首先为每个保证不同的类型定义一个函数,然后为 "conflictable" 类型定义一个概念顺序,然后逐步实例化每个大小为(1) 大于 [unsigned] long long 的大小,并且 (2) 不等于位于排序靠前位置的任何冲突类型的大小。

这是一个演示:

#include <climits> // most integer limit macros, including SSIZE_MAX
#include <cstddef> // size_t, ptrdiff_t, [u]intmax_t
#include <cstdint> // SIZE_MAX, PTRDIFF_{MIN,MAX}, UINTMAX_MAX, INTMAX_{MIN,MAX}
#include <sys/types.h> // ssize_t
#include <cstdio>

// primary template
template<typename T> void f(void);

// declarations -- guaranteed not to cause conflicts; dups are allowed
template<> void f<unsigned char>(void);
template<> void f<unsigned short>(void);
template<> void f<unsigned int>(void);
template<> void f<unsigned long>(void);
template<> void f<unsigned long long>(void);
template<> void f<size_t>(void);
template<> void f<uintmax_t>(void);
template<> void f<char>(void);
template<> void f<short>(void);
template<> void f<int>(void);
template<> void f<long>(void);
template<> void f<long long>(void);
template<> void f<ssize_t>(void);
template<> void f<ptrdiff_t>(void);
template<> void f<intmax_t>(void);

int main(void) {
    f<unsigned char>();
    f<unsigned short>();
    f<unsigned int>();
    f<unsigned long>();
    f<unsigned long long>();
    f<size_t>();
    f<uintmax_t>();
    f<char>();
    f<short>();
    f<int>();
    f<long>();
    f<long long>();
    f<ssize_t>();
    f<ptrdiff_t>();
    f<intmax_t>();
    return 0;
} // end main()

// definitions -- must use preprocessor guard on conflictable types
template<> void f<unsigned char>(void) { std::printf("%d\n",1); }
template<> void f<unsigned short>(void) { std::printf("%d\n",2); }
template<> void f<unsigned int>(void) { std::printf("%d\n",3); }
template<> void f<unsigned long>(void) { std::printf("%d\n",4); }
template<> void f<unsigned long long>(void) { std::printf("%d\n",5); }
#if SIZE_MAX > ULLONG_MAX
template<> void f<size_t>(void) { std::printf("%d\n",6); }
#endif
#if UINTMAX_MAX > ULLONG_MAX && UINTMAX_MAX != SIZE_MAX
template<> void f<uintmax_t>(void) { std::printf("%d\n",7); }
#endif
template<> void f<char>(void) { std::printf("%d\n",8); }
template<> void f<short>(void) { std::printf("%d\n",9); }
template<> void f<int>(void) { std::printf("%d\n",10); }
template<> void f<long>(void) { std::printf("%d\n",11); }
template<> void f<long long>(void) { std::printf("%d\n",12); }
#if SSIZE_MAX > LLONG_MAX
template<> void f<ssize_t>(void) { std::printf("%d\n",13); }
#endif
#if PTRDIFF_MAX > LLONG_MAX && PTRDIFF_MAX != SSIZE_MAX
template<> void f<ptrdiff_t>(void) { std::printf("%d\n",14); }
#endif
#if INTMAX_MAX > LLONG_MAX && INTMAX_MAX != SSIZE_MAX && INTMAX_MAX != PTRDIFF_MAX
template<> void f<intmax_t>(void) { std::printf("%d\n",15); }
#endif

我系统上的输出:

1
2
3
4
5
4
4
8
9
10
11
12
11
11
11

事实证明,在我的系统上,所有冲突类型实际上都与真实类型 unsigned longlong.

冲突

此解决方案的几个限制是它只能用于具有相应 *_MAX 宏的类型,并且不适用于浮点类型,因为预处理器不支持浮点-点运算和比较。