SFINAE和定义顺序

SFINAE and order of definition

本文关键字:顺序 定义 SFINAE      更新时间:2023-10-16

考虑这个简单的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::beginstd::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_assertconst ns::foo留下ns::foo::beginconst