4

I want to check for the existence of a function in a specific namespace using SFINAE. I have found SFINAE to test a free function from another namespace which does the job, but there are some things I don't understand.

Currently I have this working code, straight from the linked question:

// switch to 0 to test the other case
#define ENABLE_FOO_BAR 1

namespace foo {
  #if ENABLE_FOO_BAR
    int bar();
  #endif
}

namespace detail_overload {
  template<typename... Args> void bar(Args&&...);
}
namespace detail {
  using namespace detail_overload;
  using namespace foo;
  template<typename T> decltype(bar()) test(T);
  template<typename> void test(...);
}
static constexpr bool has_foo_bar = std::is_same<decltype(detail::test<int>(0)), int>::value;

static_assert(has_foo_bar == ENABLE_FOO_BAR, "something went wrong");

(the ENABLE_FOO_BAR macro is just for testing purpose, in my real code I don't have such a macro available otherwise I wouldn't be using SFINAE)


However, as soon as I put detail_overload::bar() in any other namespace (adjusting the using directive as needed), the detection breaks silently and the static_assert kicks in when foo::bar() exists. It only works when the "dummy" bar() overload is directly in the global namespace, or part of the ::detail_overload namespace (note the global :: scope).

// breaks
namespace feature_test {
  namespace detail_overload {
    template<typename... Args> void bar(Args&&...);
  }
  namespace detail {
    using namespace detail_overload;
    using namespace foo;
    //...

// breaks
namespace feature_test {
  template<typename... Args> void bar(Args&&...);
  namespace detail {
    using namespace foo;
    //...

// breaks
namespace detail {
  namespace detail_overload {
    template<typename... Args> void bar(Args&&...);
  }
  using namespace detail_overload;
  using namespace foo;
  //...

// works
template<typename... Args> void bar(Args&&...);
namespace feature_test {
  namespace detail {
    using namespace foo;
    //...

// works
namespace detail_overload {
  template<typename... Args> void bar(Args&&...);
}
namespace feature_test {
  namespace detail {
    using namespace detail_overload;
    using namespace foo;
    //...

I realize this is the very same problem as the question I linked to, and as mentioned I already have a working solution, but what is not addressed there is why precisely does this happen?

As a side question, is there any way to achieve correct SFINAE detection without polluting the global namespace with either bar() or a detail_overload namespace? As you can guess from the non-working examples, I'd like to neatly wrap everything in a single feature_test namespace.

Community
  • 1
  • 1
syam
  • 14,701
  • 3
  • 41
  • 65
  • `detail::test(0)`: for the second overload of `test`, the template parameter cannot be deduced (so it can never be selected). Try `detail::test(0)`. – dyp Sep 21 '13 at 14:58
  • @DyP changed the call to `detail::test(0)`, no luck: the `static_assert` still kicks in when `foo::bar()` exists and the "dummy" overload is somewhere else than `::bar()` or `::detail_overload::bar()`. – syam Sep 21 '13 at 15:03
  • 1
    [namespace.udir]/2 "During unqualified name lookup (3.4.1), the names appear as if they were declared in the nearest enclosing namespace which contains both the *using-directive* and the nominated namespace." So they appear in different namespaces, and name hiding occurs. – dyp Sep 21 '13 at 15:17
  • @DyP Damn, that's quite obvious in hindsight. So I guess there's no way to avoid polluting the global namespace. Injecting the `detail_overload` namespace into `foo` (and then `using namespace foo::detail_overload;`) indeed works but since I don't own `foo` it'd be quite a cavalier thing to do, to say the least. Anyway, care to make an answer of your latest comment, since it explains the problem I was facing? – syam Sep 21 '13 at 15:27
  • 1
    If your function `bar` took any arguments, you could make use of dependent name lookup to make it work (w/o a second overload of `bar`). – dyp Sep 21 '13 at 15:57
  • @DyP Indeed, dependent name lookup works like a charm (`bar` *does* take arguments in my real code). Thanks again! For what it's worth I posted an answer with the code modified as per your suggestion. – syam Sep 21 '13 at 16:55

2 Answers2

3

I'll change it slightly so the fall-back declaration of bar isn't a template (= shorter code), and don't use SFINAE as this is purely a name lookup issue.

namespace foo {
    int bar(int);
}

namespace feature_test {
    namespace detail_overload {
        void bar(...);
    }

    namespace detail {
        using namespace detail_overload;
        using namespace foo;

        void test() { bar(0); } // (A)
    }
}

In line (A), the compiler needs to find the name bar. How is it looked up? It's not argument-dependent, so it must be unqualified lookup: [basic.lookup.unqual]/2

The declarations from the namespace nominated by a using-directive become visible in a namespace enclosing the using-directive; see 7.3.4. For the purpose of the unqualified name lookup rules described in 3.4.1, the declarations from the namespace nominated by the using-directive are considered members of that enclosing namespace.

Note they become in an enclosing namespace, not the enclosing namespace. The details from [namespace.udir]/2 reveal the issue:

[...] During unqualified name lookup (3.4.1), the names appear as if they were declared in the nearest enclosing namespace which contains both the using-directive and the nominated namespace.

That is, for the name lookup of bar inside test:

namespace foo {
    int bar(int);
}

// as if
using foo::bar;
namespace feature_test {
    namespace detail_overload {
        void bar(...);
    }

    // as if
    using detail_overload::bar;
    namespace detail {
        // resolved
        // using namespace detail_overload;
        // using namespace foo;

        void test() { bar(0); } // (A)
    }
}

Therefore, the name bar found in feature_test hides the name (not) found in the global scope.

Note: Maybe you can hack around this issue with argument-dependent name lookup (and a second SFINAE). If something comes to my mind, I'll add it.

dyp
  • 38,334
  • 13
  • 112
  • 177
  • *Pedantic note:* Technically, it's argument-dependent name lookup, where the sets of associated namespaces and classes are empty. ADL uses unqualified lookup, and in this case, it effectively falls back to unqualified lookup. – dyp Sep 21 '13 at 15:37
  • Thanks for the detailed explanation. Indeed the SFINAE part got me confused and I didn't see it was "just" a name lookup issue. – syam Sep 21 '13 at 15:40
2

In addition to DyP's answer and following his comment:

If your function bar took any arguments, you could make use of dependent name lookup to make it work (w/o a second overload of bar).

Indeed in my real code bar() does take arguments.

As a side question, is there any way to achieve correct SFINAE detection without polluting the global namespace...

So yes, dependent name lookup works like a charm. For the sake of completeness, and in case it can help others in the future, here's my now perfectly working code:

#define ENABLE_FOO_BAR 1

namespace foo {
  #if ENABLE_FOO_BAR
    int bar(int);
  #endif
}

namespace feature_test {
  namespace detail {
    using namespace foo;
    template<typename T> decltype(bar(std::declval<T>())) test(int);
    template<typename> void test(...);
  }
  static constexpr bool has_foo_bar = std::is_same<decltype(detail::test<int>(0)), int>::value;
  static_assert(has_foo_bar == ENABLE_FOO_BAR, "something went wrong");
}

All credit goes to DyP, I don't believe I'd have thought about this by myself.

Community
  • 1
  • 1
syam
  • 14,701
  • 3
  • 41
  • 65