Как 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() {}
Upd ate:
Из комментариев (здесь и в 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>()
, потому что проблема со стабильностью.Чтобы это увидеть, положите declval
назад и попробуйте static_assert
за const ns::foo
, оставив ns::foo::begin
не const
.
Неопределенное поведение для добавления специализаций к шаблону после того, как шаблон уже был использован в другом месте. –
@KerrekSB: Вы уверены, что это актуально в этом случае? В конце концов, первое использование, которое вызовет неявное создание, будет в 'static_assert' –
@AndyProwl: я уверен, что на 90% или около того. Я считаю, что это обсуждалось раньше, и это тот вывод, к которому мы пришли. Конечно, я ошибаюсь. –