SFINAE和定义顺序
SFINAE and order of definition
考虑这个简单的SFINAE测试,以确定一个类型是否可以作为std::begin
的参数
#include <utility>
template <class T> constexpr auto
std_begin_callable (T const*) -> decltype (std::begin (std::declval <T>()), bool ())
{ return true; }
template <class> constexpr bool
std_begin_callable (...)
{ return false; }
#include <array>
static_assert (std_begin_callable <std::array <int, 3>> (0), "failed");
int main () {}
注意array
标头,其中定义了std::begin
的专门化,在SFINAE函数之后包含。断言失败。现在如果我之前移动#include <array>
,它会工作。(gcc 4.8.0 20130411, clang version 3.2)
我不明白为什么。SFINAE函数是模板,不应该在需要时实例化它们,在静态断言中,在包含定义它们测试的函数的头文件之后?
问题是我的SFINAE在标头中,我必须确保它在任何其他容器标头之后包含(这个问题没有特别链接到array
标头)。
正如Xeo所说的,要使其工作,您必须#include <iterator>
引入适当的begin
定义。更准确地说,这是可行的:
#include <iterator>
#include <utility>
template <class T> constexpr auto
std_begin_callable (T const*) -> decltype (std::begin (std::declval <T>()), bool ())
{ return true; }
template <class> constexpr bool
std_begin_callable (...)
{ return false; }
#include <array>
static_assert (std_begin_callable <std::array <int, 3>> (0), "failed");
int main () {}
现在,让我们看看为什么不包含<iterator>
的原始代码编译但没有给出预期的结果(除非您将#include <array>
上移)。
包含<utility>
间接意味着包含<initializer_list>
,它定义了std::begin(std::initializer_list<T>)
。因此,在这个翻译单元中,名称std::begin
是可见的。
然而,当你调用std_begin_callable
时,第一个重载会被SFINAEd掉,因为可见的std::begin
不能占用std::array
。
现在,如果您完全删除<iterator>
和<utility>
的包含(将<array>
保留在std_begin_callable
之后),则编译失败,因为编译器将不再看到std::begin
或std::declval
的任何过载:
template <class T> constexpr auto
std_begin_callable (T const*) -> decltype (std::begin (std::declval <T>()), bool ())
{ return true; } // error: begin/declval is not a member of std
template <class> constexpr bool
std_begin_callable (...)
{ return false; }
#include <array>
static_assert (std_begin_callable <std::array <int, 3>> (0), "failed");
int main () {}
最后,您可以复制/简化前面的错误行为:
namespace std {
void begin();
template <typename T>
T&& declval();
}
template <class T> constexpr auto
std_begin_callable (T const*) -> decltype (std::begin (std::declval <T>()), bool ())
{ return true; } // No compiler error here, just SFINAE.
template <class> constexpr bool
std_begin_callable (...)
{ return false; }
#include <array>
static_assert (std_begin_callable <std::array <int, 3>> (0), "failed");
int main () {}
更新:
从评论(这里和OP)我猜这是不可能解决头文件的顺序问题,你想要的方式。然后,让我建议一个基于ADL的变通方法,它接近于解决方案,并且可能(但可能不是)对您的用例足够好:
// <your_header_file>
#include <iterator>
#include <utility>
namespace detail {
using std::begin;
template <typename T, typename = decltype(begin(*((T*)0)))>
constexpr std::true_type std_begin_callable(int) { return std::true_type(); }
template <typename>
constexpr std::false_type std_begin_callable(long) { return std::false_type(); };
};
template <typename T>
constexpr auto std_begin_callable() ->
decltype(detail::std_begin_callable<typename std::remove_reference<T>::type>(0)) {
return detail::std_begin_callable<typename std::remove_reference<T>::type>(0);
}
// </your_header_file>
// <a_supposedly_std_header_file>
namespace std {
struct foo { int begin() /* const */; };
struct bar;
int begin(/*const*/ bar&);
template <typename T> struct goo;
template <typename T>
int begin(/*const*/ goo<T>&);
}
// </a_supposedly_std_header_file>
// <a_3rd_party_header_file>
namespace ns {
struct foo { int begin() /*const*/; };
struct bar;
int begin(/*const*/ bar&);
template <typename T> struct goo;
template <typename T>
int begin(/*const*/ goo<T>&);
}
// </a_3rd_party_header_file>
//<some_tests>
static_assert ( std_begin_callable</*const*/ std::foo>(), "failed");
static_assert ( std_begin_callable</*const*/ std::bar>(), "failed");
static_assert ( std_begin_callable</*const*/ std::goo<int>>(), "failed");
static_assert ( std_begin_callable</*const*/ ns::foo>(), "failed");
static_assert ( std_begin_callable</*const*/ ns::bar>(), "failed");
static_assert ( std_begin_callable</*const*/ ns::goo<int>>(), "failed");
//</some_tests>
int main () {}
它似乎有效,但我还没有完全测试过。我建议你在代码中尝试几种有/没有注释的const
s的组合。
我使用*((T*)0)
而不是std::declval<T>()
,因为constness问题。要查看它,将declval
放回去,并尝试static_assert
为const ns::foo
留下ns::foo::begin
非const
。
- c++11评估顺序(未定义的行为)
- 为什么用户定义的函数不按照给定的顺序对相同长度的元素进行排序?
- 在自定义程序中使用本机 Windows 自然顺序排序
- 为什么更改包含 psapi.h 的顺序会产生编译错误?(标识符 BOOL 未定义)
- 交换未定义数据类型中的字节顺序
- 用户定义的转换顺序(以C++为单位)
- 在C++中,当表达式涉及对象时,将表达式赋值到对象中时,是否有定义的操作顺序?
- 为什么在未由语言本身定义的结构字节中的位字段顺序
- 是定义的函数参数的内部执行顺序
- 为什么在定义静态成员变量时不遵循定义顺序
- 顺序自定义类型特征的行为就像一个
- 如何在使用 std::make_tuple 时避免构造函数的未定义执行顺序
- 初始化变量列表中的赋值顺序是否未定义
- 函数签名、调用和定义的顺序
- c++如何定义其他开发人员需要调用的常量函数顺序
- 定义的参数求值顺序会导致次优代码
- 对定义顺序的向量的子集进行高效排序
- C++ 自定义映射键/值不按顺序排列
- 全局常量定义的顺序
- 更改专用化"template<...>"行中模板参数的顺序是否会导致定义重复或歧义?