【问题标题】:C++ enforce second-pass name lookup in template functionC++ 在模板函数中强制执行第二遍名称查找
【发布时间】:2015-01-28 07:52:08
【问题描述】:

有没有办法强制 C++ 编译器在模板实例化期间(而不是之前)对给定符号执行名称查找?

给定以下代码:

template <class T>
auto wrapper( T t ) -> decltype( f( t ) )
{
    return f( t );
}

unsigned char f( int x ) { return x % 256; }
unsigned char f( unsigned char x ) { return x; }

int main( int, char ** )
{
    auto x = wrapper( 3100 );
    return 0;
}

我能做些什么(除了将f 的定义移到顶部)以使该代码编译并给出相同的结果,就好像f 的所有定义在@ 的定义之前都可用一样987654324@?

我找不到任何东西,可能是因为我不知道如何正确表达这个问题。 f 的所有参数类型都可以假定为用户定义的类型,如果这有帮助的话。

【问题讨论】:

  • 为什么不使用模板专业化?
  • @Chiel:谢谢,好主意!所以我会用类模板替换f?这行得通,但我希望有一个不需要我更改f 定义的解决方案(很多)。还是你有别的想法?
  • 您可以使用适用于大多数情况的默认设置,并在需要时使用专业化,请参阅我的回答。

标签: c++ templates name-lookup


【解决方案1】:

是否有某种方法可以强制 C++ 编译器执行名称查找 在模板实例化期间(而不是之前)给定符号?

是的。首先,名称必须是依赖的。 wrapper 中的名称 f 在用作 f(t) 时是依赖的,因为 t 是依赖于类型的。 [temp.dep]/1:

在一个表达式中:

        后缀表达式 ( 表达式列表 opt)

如果 postfix-expression 是一个 unqualified-id,则 unqualified-id 表示一个从属名称 if

  • 表达式列表中的任何表达式都是包扩展 (14.5.3),
  • 表达式列表中的任何表达式都是依赖于类型的表达式 (14.6.2.2),或者
  • 如果 unqualified-id 是一个 template-id,其中任何模板参数都依赖于模板参数。

问题在于,在模板本身之后声明的名称,即仅在实例化而不是定义上下文中,只能使用 argumentdependent name lookup 找到。您的 f 重载仅采用基本类型,但根据 [basic.lookup.argdep]/2,那些没有与之关联的全局命名空间:

如果T 是基本类型,则它的关联命名空间集和 类都是空的。

因此,如果参数与参数的类型相同,则永远找不到您声明的fs。一个小技巧会有所帮助:

template <typename T>
struct refwrap
{
    T&& t;
    refwrap(T&& t) : t(std::forward<T>(t)) {}
    operator T&&() {return std::forward<T>(t);}
};

template <typename T>
auto make_refwrap( T&& t ) -> refwrap<T> // making use of reference collapsing
{ return {std::forward<T>(t)}; }         // inside refwrap to get forwarding

当在全局命名空间中声明此模板时,将导致 ADL 考虑它。改写wrapper如下:

template <class T>
auto wrapper( T t ) -> decltype( f( make_refwrap(t) ) )
{
    return f( make_refwrap(t) );
}

Demo。但是,这不是正确的方法,因为它在更复杂的情况下会失败。

【讨论】:

  • 啊,现在我明白了导致提出这个问题的原始问题是什么。我的 f 函数是在不同的命名空间中定义的(即与 wrapper 相同)然后是参数。
  • 哪个条款说它只能在 ADL 中找到?另外,“正确”的方法是什么?
【解决方案2】:

这适用于模板专业化。请注意,您必须决定默认功能是什么,因为我看不到它有问题。

// default function
template <class T>
unsigned char f( T x ) { return x; }

// specialization for int
template <>
unsigned char f( int x ) { return x % 256; }

int main( int, char ** )
{
    auto x = f( 3100 );
    return 0;
}

【讨论】:

  • 我的例子太简单了,很容易被误解。请不要假设存在默认情况。请不要删除 wrapper 函数,因为问题确实与该函数有关,在我的情况下,它当然比身份函数更复杂。
  • 为什么简单的重载还不够?
  • wrapper 函数应该使用另一个函数 f 来获得中间结果并转换该结果。为了定义wrapperf 的所有定义必须在定义wrapper 时可用,因为在解析模板定义时会查找名称。在我的情况下,这是不受欢迎的行为,因为该库的用户应该能够提供 f 的其他定义,而且我很难告诉他们在包含某些其他文件之前必须包含他们的定义。
  • 但是你不能用一个带有函数指针的容器,用户可以在其中注册函数吗?函数注册时是编译时还是运行时?
【解决方案3】:

以下代码不是很干净,但说明了如何使用类模板特化来解决问题。它保持原来的界面(即fwrapper可以像以前一样使用)。

感谢您给我正确的提示。我愿意接受一个不那么冗长的解决方案。

#include <type_traits>

template <class ...>
struct F;

template <class T>
auto wrapper( T t )
    -> decltype( F<typename std::decay<T>::type>::f( t ) )
{
    return F<typename std::decay<T>::type>::f( t );
}

template <>
struct F<unsigned char>
{
    static unsigned char f( unsigned char x ) { return x; }
};

template <>
struct F<int>
{
    static unsigned char f( int x ) { return x % 256; }
};

template <class T>
auto f( T t )
    -> decltype( F<typename std::decay<T>::type>::f( t ) )
{
    return F<typename std::decay<T>::type>::f( t );
}

int main( int, char ** )
{
    auto x = wrapper( 3100 );
    return 0;
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-02-05
    • 2021-09-25
    • 1970-01-01
    • 2023-03-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多