O que (eu acho) eu preciso
Como posso definir uma característica de tipo que verifica se, para um tipo T
, a função ::foo(T)
é declarada?
O que estou achando difícil é ter ::foo
uma maneira amigável ao SFINAE. Por exemplo, se o compilador chegou ao ponto em que o seguinte é definido,
template<typename T>
void f(T t) { foo(t); }
está tudo bem se nada foo
foi visto até agora.
Mas assim que mudo foo
para ::foo
, recebo um erro grave.
O caso de uso (caso você ache que não preciso do acima)
Tenho um ponto de personalização como este:
// this is in Foo.hpp
namespace foos {
inline constexpr struct Foo {
template <typename T, std::enable_if_t<AdlFooable<std::decay_t<T>>::value, int> = 0>
constexpr bool operator()(T const& x) const {
return foo(x);
}
} foo{};
}
que permite personalizar o comportamento de uma chamada foos::foo
definindo uma foo
sobrecarga ADL para o tipo desejado.
A definição da característica é simples:
// this is in Foo.hpp
template <typename T, typename = void>
struct AdlFooable : std::false_type {};
template <typename T>
struct AdlFooable<T, std::void_t<decltype(foo(std::declval<T const&>()))>>
: std::true_type {};
Dado que, se alguém define
// this is in Bar.hpp
namespace bar {
struct Bar {};
bool foo(bar::Bar);
}
então uma chamada para
// other includes
#include "Foo.hpp"
#include "Bar.hpp"
bool b = foos::foo(bar::Bar{});
funciona como esperado, com foos::foo
roteamento da chamada para bar::foo(bar::Bar)
, que é encontrado via ADL. E isso funciona bem independentemente da ordem dos dois #include
s, o que é bom, porque eles podem ser incluídos em qualquer ordem, se // other includes
transitivamente os incluir.
Até agora tudo bem.
O que eu realmente não gosto dessa abordagem é que alguém poderia definir erroneamente, em vez do segundo trecho acima, o seguinte,
// this is in Bar.hpp
namespace bar {
struct Bar {};
}
bool foo(bar::Bar);
com foo
escopo global.
Nesse caso, se o #include "Bar.hpp"
vier antes de #include "Foo.hpp"
, o programa de código funcionará, porque o corpo de Foo::operator()
será selecionado ::foo(bar::Bar)
por pesquisa comum.
Mas assim que a ordem do #include
s é invertida, o código quebra.
Sim, o bug está na definição foo(bar::Bar)
do namespace global, mas acho que também é um bug que pode passar despercebido por puro acaso.
É por isso que eu gostaria de mudar o traço de tipo para expressar que "uma chamada não qualificada para foo(T)
é encontrada, mas não por meio de uma pesquisa comum " ou, mais diretamente, " foo(std::declval<T>())
deve compilar, mas não::foo(std::declval<T>())
deve compilar" .
A maneira comum de fazer isso é chamar a implementação de customização somente via ADL. Então a sobrecarga
::foo(bar::Bar)
não é considerada em nenhum caso, independentemente da ordem de inclusão.E a maneira de fazer isso é incluir uma pílula de veneno:
A pesquisa comum de
foo
encontraráfoos::detail::foo
qual não funcionará. Ela não pode examinar o escopo global. Portanto, afoo
chamada só pesquisará via ADL.Editar:
Isso na verdade diz a você como formar um trait que
foo(x)
é somente chamável por ADL, a pergunta nominal. E isto é, sefoos::foo(x)
é chamável com esta implementação, entãofoo(x)
é chamável via ADL somente.