4

我想使用 SFINAE 检查特定命名空间中是否存在函数。我发现SFINAE 可以测试来自另一个命名空间的自由函数,它可以完成这项工作,但有些事情我不明白。

目前我有这个工作代码,直接来自链接的问题:

// 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");

(该ENABLE_FOO_BAR宏仅用于测试目的,在我的真实代码中我没有这样的宏,否则我不会使用 SFINAE)


但是,一旦我放入detail_overload::bar()任何其他命名空间(using根据需要调整指令),检测就会静默中断,并且static_assert存在foo::bar()启动。它仅在“虚拟”bar()重载直接位于全局命名空间或命名空间的一部分::detail_overload(注意全局::范围)时才有效。

// 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;
    //...

我意识到这与我链接的问题完全相同,并且如前所述,我已经有了一个可行的解决方案,但是没有解决的问题是为什么会发生这种情况?

作为一个附带问题,有没有办法在不污染全局命名空间的情况下实现正确的 SFINAEbar()检测detail_overload?正如您可以从非工作示例中猜到的那样,我想将所有内容整齐地包装在一个feature_test命名空间中。

4

2 回答 2

3

我会稍微改变它,所以后备声明bar不是模板(= 更短的代码),并且不要使用 SFINAE,因为这纯粹是一个名称查找问题。

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)
    }
}

在 (A) 行,编译器需要找到 name bar。它是如何查找的?它不依赖于参数,所以它必须是不合格的查找:[basic.lookup.unqual]/2

由using-directive指定的命名空间中的声明在包含using-directive的命名空间中变得可见;见 7.3.4。出于 3.4.1 中描述的非限定名称查找规则的目的,来自using 指令指定的名称空间的声明被视为该封闭名称空间的成员。

请注意,它们成为封闭命名空间,而不是封闭命名空间。[namespace.udir]/2 的详细信息揭示了这个问题:

[...] 在非限定名称查找 (3.4.1) 期间,名称看起来好像它们是在最近的封闭命名空间中声明的,其中包含使用指令和指定命名空间。

也就是说,对于barinside的名称查找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)
    }
}

因此,在bar中找到的名称feature_test隐藏了在全局范围内找到的名称(未找到)。

注意:也许你可以通过参数相关的名称查找(和第二个 SFINAE)来解决这个问题。如果我想到了什么,我会添加它。

于 2013-09-21T15:32:46.390 回答
2

除了 DyP 的回答和他的评论之外:

如果您的函数bar接受任何参数,您可以使用依赖名称查找来使其工作(w/oa 第二个重载bar)。

事实上,在我的真实代码bar() 中确实需要参数。

作为一个附带问题,有没有办法在不污染全局命名空间的情况下实现正确的 SFINAE 检测......

所以是的,从属名称查找就像一个魅力。为了完整起见,并以防将来可以帮助其他人,这是我现在完美运行的代码:

#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");
}

所有功劳都归功于 DyP,我不相信我自己会想到这一点。

于 2013-09-21T16:53:19.003 回答