admin管理员组

文章数量:1025465

What (I think) I need

How can I define a type trait that checks whether, for a type T, the function ::foo(T) is declared?

What I'm finding hard, is to have ::foo in SFINAE friendly way. For instance, if the compiler has got to the point where the following is defined,

template<typename T>
void f(T t) { foo(t); }

it's prefectly fine if no foo whatsover has been seen so far.

But as soon as I change foo to ::foo, then I get a hard error.

The use case (in case you think I don't need the above)

I have a customization point like this:

// 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{};
}

that allows customizing the behavior of a call to foos::foo by defining an ADL foo overload for the desired type.

The definition of the trait is straightforward:

// 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 {};

Given that, if one defines

// this is in Bar.hpp
namespace bar {
    struct Bar {};
    bool foo(bar::Bar);
}

then a call to

// other includes
#include "Foo.hpp"
#include "Bar.hpp"
bool b = foos::foo(bar::Bar{});

works as expected, with foos::foo routing the call to bar::foo(bar::Bar), which is found via ADL. And this works well regardless of the order of the two #includes, which is good, because they might be included in either order, if // other includes transitively includes them.

So far so good.

What I don't really like of this approach, is that one could mistakenly define, instead of the second snippet above, the following,

// this is in Bar.hpp
namespace bar {
    struct Bar {};
}
bool foo(bar::Bar);

with foo in global scope.

In this case, if the #include "Bar.hpp" happens to come before #include "Foo.hpp", the code program will work, because the body of Foo::operator() will pick ::foo(bar::Bar) by ordinary lookup.

But as soon as the order of the #includes happens to be reversed, then the code breaks.

Yes, the bug is in defining foo(bar::Bar) in global namespace, but I think it's also a bug that it can pass unnoticed by pure chance.

That's why I would like to change the type trait to express that "an unqualified call to foo(T) is found, but not via ordinary lookup", or, more directly, "foo(std::declval<T>()) must compile, but ::foo(std::declval<T>()) must not compile".

A non-solution for those who are curious

After I accepted the answer, I have re-read relevant parts of Josuttis' C++ Templates - The Complete Guide and found that ADL can be inhibited by a mean other than qualifying the function, i.e. wrapping the name of the unqualified function in parenthesis, e.g. while foo(args...) undergoes ADL, (foo)(args...) doesn't!

Unfortunately, that just means that the name foo is looked up as if it was fully qualified (I presume with the current namespace), and so the attempt fails the same way as described above.

What (I think) I need

How can I define a type trait that checks whether, for a type T, the function ::foo(T) is declared?

What I'm finding hard, is to have ::foo in SFINAE friendly way. For instance, if the compiler has got to the point where the following is defined,

template<typename T>
void f(T t) { foo(t); }

it's prefectly fine if no foo whatsover has been seen so far.

But as soon as I change foo to ::foo, then I get a hard error.

The use case (in case you think I don't need the above)

I have a customization point like this:

// 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{};
}

that allows customizing the behavior of a call to foos::foo by defining an ADL foo overload for the desired type.

The definition of the trait is straightforward:

// 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 {};

Given that, if one defines

// this is in Bar.hpp
namespace bar {
    struct Bar {};
    bool foo(bar::Bar);
}

then a call to

// other includes
#include "Foo.hpp"
#include "Bar.hpp"
bool b = foos::foo(bar::Bar{});

works as expected, with foos::foo routing the call to bar::foo(bar::Bar), which is found via ADL. And this works well regardless of the order of the two #includes, which is good, because they might be included in either order, if // other includes transitively includes them.

So far so good.

What I don't really like of this approach, is that one could mistakenly define, instead of the second snippet above, the following,

// this is in Bar.hpp
namespace bar {
    struct Bar {};
}
bool foo(bar::Bar);

with foo in global scope.

In this case, if the #include "Bar.hpp" happens to come before #include "Foo.hpp", the code program will work, because the body of Foo::operator() will pick ::foo(bar::Bar) by ordinary lookup.

But as soon as the order of the #includes happens to be reversed, then the code breaks.

Yes, the bug is in defining foo(bar::Bar) in global namespace, but I think it's also a bug that it can pass unnoticed by pure chance.

That's why I would like to change the type trait to express that "an unqualified call to foo(T) is found, but not via ordinary lookup", or, more directly, "foo(std::declval<T>()) must compile, but ::foo(std::declval<T>()) must not compile".

A non-solution for those who are curious

After I accepted the answer, I have re-read relevant parts of Josuttis' C++ Templates - The Complete Guide and found that ADL can be inhibited by a mean other than qualifying the function, i.e. wrapping the name of the unqualified function in parenthesis, e.g. while foo(args...) undergoes ADL, (foo)(args...) doesn't!

Unfortunately, that just means that the name foo is looked up as if it was fully qualified (I presume with the current namespace), and so the attempt fails the same way as described above.

Share Improve this question edited Nov 20, 2024 at 18:03 Enlico asked Nov 18, 2024 at 10:30 EnlicoEnlico 28.9k8 gold badges67 silver badges152 bronze badges 7
  • Can you use requires from C++20? – Jarod42 Commented Nov 18, 2024 at 10:48
  • You have a traits for foo(T), you can expend it for ::foo(T), then AdlFooAble<T>::value && !GlobalFooAble::value. – Jarod42 Commented Nov 18, 2024 at 10:51
  • @Jarod42 ::foo doesn't seem to be SFINAE friendly godbolt./z/vnhsh6d37 or am I misunderstanding your comment? – cigien Commented Nov 18, 2024 at 11:16
  • @Jarod42, no, C++17, unfortunately. – Enlico Commented Nov 18, 2024 at 11:22
  • @cigien, yeah, that's my difficulty: even if the actual type plugged in T is not known when you define the GlobalFooable trait, ::foo lookup doesn't seem to be delayed as foo does. I guess this is just how the language works, and I might ask another question with language-lawyer tag to ask specifically about what part of the standard determines this behavior, but here I'm looking for a solution/workaround. – Enlico Commented Nov 18, 2024 at 11:31
 |  Show 2 more comments

1 Answer 1

Reset to default 9

The ordinary way to do this is to call the customization implementation only via ADL. So the overload ::foo(bar::Bar) is not considered in any case, regardless of include order.

And the way to do this is to include a poison pill:

namespace foos {
namespace detail {
void foo() = delete;
struct Foo {
    template <typename T>
    constexpr auto operator()(T const& x) const -> decltype(foo(x)) {
        return foo(x);
    }
};
}
inline constexpr detail::Foo foo{};
}

The ordinary lookup of foo will find foos::detail::foo which will not work. It cannot examine the global scope. Therefore, the foo call will only lookup via ADL.


Edit:

This actually tells you how to form a trait that foo(x) is only callable by ADL, the nominal question. And that is, if foos::foo(x) is callable with this implementation, then foo(x) is callable via ADL only.

What (I think) I need

How can I define a type trait that checks whether, for a type T, the function ::foo(T) is declared?

What I'm finding hard, is to have ::foo in SFINAE friendly way. For instance, if the compiler has got to the point where the following is defined,

template<typename T>
void f(T t) { foo(t); }

it's prefectly fine if no foo whatsover has been seen so far.

But as soon as I change foo to ::foo, then I get a hard error.

The use case (in case you think I don't need the above)

I have a customization point like this:

// 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{};
}

that allows customizing the behavior of a call to foos::foo by defining an ADL foo overload for the desired type.

The definition of the trait is straightforward:

// 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 {};

Given that, if one defines

// this is in Bar.hpp
namespace bar {
    struct Bar {};
    bool foo(bar::Bar);
}

then a call to

// other includes
#include "Foo.hpp"
#include "Bar.hpp"
bool b = foos::foo(bar::Bar{});

works as expected, with foos::foo routing the call to bar::foo(bar::Bar), which is found via ADL. And this works well regardless of the order of the two #includes, which is good, because they might be included in either order, if // other includes transitively includes them.

So far so good.

What I don't really like of this approach, is that one could mistakenly define, instead of the second snippet above, the following,

// this is in Bar.hpp
namespace bar {
    struct Bar {};
}
bool foo(bar::Bar);

with foo in global scope.

In this case, if the #include "Bar.hpp" happens to come before #include "Foo.hpp", the code program will work, because the body of Foo::operator() will pick ::foo(bar::Bar) by ordinary lookup.

But as soon as the order of the #includes happens to be reversed, then the code breaks.

Yes, the bug is in defining foo(bar::Bar) in global namespace, but I think it's also a bug that it can pass unnoticed by pure chance.

That's why I would like to change the type trait to express that "an unqualified call to foo(T) is found, but not via ordinary lookup", or, more directly, "foo(std::declval<T>()) must compile, but ::foo(std::declval<T>()) must not compile".

A non-solution for those who are curious

After I accepted the answer, I have re-read relevant parts of Josuttis' C++ Templates - The Complete Guide and found that ADL can be inhibited by a mean other than qualifying the function, i.e. wrapping the name of the unqualified function in parenthesis, e.g. while foo(args...) undergoes ADL, (foo)(args...) doesn't!

Unfortunately, that just means that the name foo is looked up as if it was fully qualified (I presume with the current namespace), and so the attempt fails the same way as described above.

What (I think) I need

How can I define a type trait that checks whether, for a type T, the function ::foo(T) is declared?

What I'm finding hard, is to have ::foo in SFINAE friendly way. For instance, if the compiler has got to the point where the following is defined,

template<typename T>
void f(T t) { foo(t); }

it's prefectly fine if no foo whatsover has been seen so far.

But as soon as I change foo to ::foo, then I get a hard error.

The use case (in case you think I don't need the above)

I have a customization point like this:

// 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{};
}

that allows customizing the behavior of a call to foos::foo by defining an ADL foo overload for the desired type.

The definition of the trait is straightforward:

// 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 {};

Given that, if one defines

// this is in Bar.hpp
namespace bar {
    struct Bar {};
    bool foo(bar::Bar);
}

then a call to

// other includes
#include "Foo.hpp"
#include "Bar.hpp"
bool b = foos::foo(bar::Bar{});

works as expected, with foos::foo routing the call to bar::foo(bar::Bar), which is found via ADL. And this works well regardless of the order of the two #includes, which is good, because they might be included in either order, if // other includes transitively includes them.

So far so good.

What I don't really like of this approach, is that one could mistakenly define, instead of the second snippet above, the following,

// this is in Bar.hpp
namespace bar {
    struct Bar {};
}
bool foo(bar::Bar);

with foo in global scope.

In this case, if the #include "Bar.hpp" happens to come before #include "Foo.hpp", the code program will work, because the body of Foo::operator() will pick ::foo(bar::Bar) by ordinary lookup.

But as soon as the order of the #includes happens to be reversed, then the code breaks.

Yes, the bug is in defining foo(bar::Bar) in global namespace, but I think it's also a bug that it can pass unnoticed by pure chance.

That's why I would like to change the type trait to express that "an unqualified call to foo(T) is found, but not via ordinary lookup", or, more directly, "foo(std::declval<T>()) must compile, but ::foo(std::declval<T>()) must not compile".

A non-solution for those who are curious

After I accepted the answer, I have re-read relevant parts of Josuttis' C++ Templates - The Complete Guide and found that ADL can be inhibited by a mean other than qualifying the function, i.e. wrapping the name of the unqualified function in parenthesis, e.g. while foo(args...) undergoes ADL, (foo)(args...) doesn't!

Unfortunately, that just means that the name foo is looked up as if it was fully qualified (I presume with the current namespace), and so the attempt fails the same way as described above.

Share Improve this question edited Nov 20, 2024 at 18:03 Enlico asked Nov 18, 2024 at 10:30 EnlicoEnlico 28.9k8 gold badges67 silver badges152 bronze badges 7
  • Can you use requires from C++20? – Jarod42 Commented Nov 18, 2024 at 10:48
  • You have a traits for foo(T), you can expend it for ::foo(T), then AdlFooAble<T>::value && !GlobalFooAble::value. – Jarod42 Commented Nov 18, 2024 at 10:51
  • @Jarod42 ::foo doesn't seem to be SFINAE friendly godbolt./z/vnhsh6d37 or am I misunderstanding your comment? – cigien Commented Nov 18, 2024 at 11:16
  • @Jarod42, no, C++17, unfortunately. – Enlico Commented Nov 18, 2024 at 11:22
  • @cigien, yeah, that's my difficulty: even if the actual type plugged in T is not known when you define the GlobalFooable trait, ::foo lookup doesn't seem to be delayed as foo does. I guess this is just how the language works, and I might ask another question with language-lawyer tag to ask specifically about what part of the standard determines this behavior, but here I'm looking for a solution/workaround. – Enlico Commented Nov 18, 2024 at 11:31
 |  Show 2 more comments

1 Answer 1

Reset to default 9

The ordinary way to do this is to call the customization implementation only via ADL. So the overload ::foo(bar::Bar) is not considered in any case, regardless of include order.

And the way to do this is to include a poison pill:

namespace foos {
namespace detail {
void foo() = delete;
struct Foo {
    template <typename T>
    constexpr auto operator()(T const& x) const -> decltype(foo(x)) {
        return foo(x);
    }
};
}
inline constexpr detail::Foo foo{};
}

The ordinary lookup of foo will find foos::detail::foo which will not work. It cannot examine the global scope. Therefore, the foo call will only lookup via ADL.


Edit:

This actually tells you how to form a trait that foo(x) is only callable by ADL, the nominal question. And that is, if foos::foo(x) is callable with this implementation, then foo(x) is callable via ADL only.

本文标签: