首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >SFINAE与定义顺序

SFINAE与定义顺序
EN

Stack Overflow用户
提问于 2013-04-23 08:35:58
回答 1查看 409关注 0票数 3

考虑一下这个简单的SFINAE测试,以确定一个类型是否可以作为std::begin的参数。

代码语言:javascript
复制
#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版本3.2)

我不明白为什么。SFINAE函数是模板,是否应该在静态断言中,在包含定义测试函数的头后,在需要时实例化它们?

问题是,我的SFINAE在头中,我必须确保它包含在任何其他容器头之后(这个问题不是专门链接到array头)。

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2013-04-24 00:31:02

正如Xeo 所说的,要使其工作,您必须#include <iterator>引入对begin的适当定义。更确切地说,这起作用是:

代码语言:javascript
复制
#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>的包含间接地意味着包含了定义std::begin(std::initializer_list<T>)<initializer_list>。因此,在这个翻译单元中,名称std::begin是可见的。

但是,当您调用std_begin_callable时,第一个重载是SFINAEd离开的,因为可见的std::begin不能接受std::array

现在,如果您完全删除<iterator><utility>的包含(在std_begin_callable之后保留<array> ),那么编译将失败,因为编译器将不再看到std::beginstd::declval的任何重载。

代码语言:javascript
复制
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 () {}

最后,您可以使用以下方法复制/简化以前的错误行为:

代码语言:javascript
复制
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的解决方案,它接近解决方案,而且(但可能不是),对您的用例来说已经足够好了:

代码语言:javascript
复制
// <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的几个组合。

我使用*((T*)0)而不是std::declval<T>(),因为这是一个一致性问题。要查看它,请将declval放回,并尝试static_assert for const ns::foo离开ns::foo::begin non-const

票数 3
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/16164650

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档