使用模板来传递未声明的类型

huangapple go评论73阅读模式
英文:

Using templates to pass undeclared types

问题

template <typename T>
void call(T in) {}

struct test {
  using type_t = int;
  void f() {
    type_t a = 1;
    call(a);
  }
};

int main() {
  test t;
  t.f();
  return 0;
}

上面的代码可以正常编译。我的问题是编译器如何正确推断Ttype_ttype_t仅在struct test的作用域内可用。

即如果将call更改为

void call(type_t in) {}

我将获得预期的错误消息:'type_t' was not declared in this scope

英文:
template &lt;typename T&gt;
void call(T in){}

struct test{
  using type_t = int;
  void f() {
    type_t a = 1;
    call(a);
  }
};

int main(){
 test t;
 t.f();
 return 0;
}

Above compiles normally. My question is how can the compiler deduce T to type_t correctly? type_t is only available inside the scope of struct test.

i.e if change call to

void call(type_t in){}

I get the expect error message of &#39;type_t&#39; was not declared in this scope.

答案1

得分: 3

test 中声明的 type_t 并不是一个问题。例如:

template <typename T>
void call(T in) {}

struct test {
    using type_t = ...;
    void f() {
        // 推断:                 T = test::type_t
        // 隐式实例化: void call<test::type_t>(test::type_t)
        call(type_t{});
    }
}

即使 type_ttest 内部是私有的,这也能正常工作,因为访问说明符不会阻止我们实例化模板。

在您的示例中,问题甚至更少,因为 type_t = int,而 type_t 不是一个独立的类型,而是一个别名。因此,编译器推断 T = int 并实例化 call<int>(int)

英文:

type_t being declared in test is not a problem. For example:

template &lt;typename T&gt;
void call(T in) {}

struct test {
    using type_t = ...;
    void f() {
        // deduces:                 T = test::type_t
        // implicitly instantiates: void call&lt;test::type_t&gt;(test::type_t)
        call(type_t{});
    }
}

Even if type_t was private within test, this would work just fine, because access-specifiers don't prevent us from instantiating templates.

In your example, it's even less of an issue, because type_t = int, and type_t isn't a distinct type but an alias. As a result, the compiler deduces T = int and instantiates call&lt;int&gt;(int).

答案2

得分: 0

type_t在这里是一个公共类型别名(因为结构体的默认可见性是公共的)。如果需要,它可以从外部访问。

即使如此,我认为如果它不是公共的,也不会改变任何事情。

您混淆了对使用您的API的人可访问的类型和对编译器可访问的类型(每种类型都可访问)。

这个区别在C中用于规避封装的缺陷:用户无法手动创建实例,但应该在函数中提供一个实例;他们只能通过特定函数(比如fopen)获取一个实例。

英文:

type_t, here, is a public type alias (because structs' default visibility is public). It is accessible from the outside if needed.

And even then, I don't think it would change anything if it weren't public.

You're confusing types that are accessible to the people who use your API, and types that are accessible to the compiler (which is every type).

This distinction was used in C to circumvent the lack of encapsulation: user's couldn't create instances "manually" but were expected to provide one in functions; they could only get an instance through specific functions (think fopen).

答案3

得分: 0

似乎你有点混淆了名字和它们所指代的事物。

举个例子,在这段代码中:

struct test{
  using type_t = int;
  void f() {
    type_t a = 1;
    call(a);
  }
};

type_t 是一个名字。它的作用域几乎像任何其他名字一样,在该作用域之外,编译器不会使用它(不是因为它不能使用,而是因为作用域的整个目的是限制名字的可见性,以便它不会被使用)。

尝试在该作用域之外使用该名字,比如:void call(type_t in){} 将会失败,简单地因为编译器找不到那个名字。我们可以通过以下两步使其生效:1) 重新安排代码,使得 type_t 在我们尝试在 call 中使用它之前已经被声明,2) 添加作用域解析运算符以便编译器可以找到它:

struct test{
  using type_t = int;
  void f();
};

void call(test::type_t in) 

void test::f() {
  type_t a = 1;
  call(a);
}

所以,要访问这些名字,我们只需安排代码,使得在尝试使用它们之前它们都已经被声明。在这种情况下,calltest 之间有足够的交互,以至于我们需要以特定的顺序安排所有内容才能使其工作(这可能不是一个很好的设计的明显迹象)。

然而,原始代码的工作方式并非如此。它并没有将名字 type_ttest 传递给 call。它只是将 type_t 声明为 int 的别名,然后将该类型的参数传递给了 call,这导致实例化和使用了 call<int>。然而,这并不依赖于名字 type_t 对模板的可见性。对于这一点,编译器只关心被引用的底层类型,而不是名字。

实际上,我们用于实例化模板的类型甚至不需要一个名字。举个例子:

template <class T>
void func(T t) { t(); }

struct foo {
    foo() {
        auto unknown_t = [] { std::cout << "lambda!\n"; };

        func(unknown_t);
    }
};

Lambda 表达式基本上创建了一个甚至没有实际名字的类-但我们仍然可以使用它的类型来实例化模板。

总结

所以,如果我们使用一个名字,那个名字必须在我们使用它的地方可见(已被编译器看到,并且在作用域内)。但是一个类型的名字并不需要对于该类型用于实例化模板是可见的。实际上,这个类型甚至不需要一个名字。

英文:

It seems to me that you're kind of confusing names with the things they refer to.

For example, in this code:

struct test{
  using type_t = int;
  void f() {
    type_t a = 1;
    call(a);
  }
};

type_t is a name. It has a scope almost like any other name, and outside that scope the compiler won't use it (not because it couldn't, but because the whole point of scopes is the limit the visibility of names so it won't).

Trying to use that name outside that scope, like: void call(type_t in){} is bound to fail simply because the compiler can't find that name. We can make it work by 1) rearranging the code so type_t has been declared by the time we try to use it in call, and 2) adding a scope resolution operator so the compiler can/will find it:

struct test{
  using type_t = int;
  void f();
};

void call(test::type_t in) 

void test::f() {
  type_t a = 1;
  call(a);
}

So, to have access to the names, we just have to arrange the code so each name has been declared before we try to use it. In this case, there's enough interaction between call and test that we need to arrange everything in a specific order for it to work (a decent sign that in real life, this may not be a great design).

That's not how the original code worked though. It didn't share the name type_t from test to call. It just declared type_t as an alias for int, and then passed a parameter of that type to call, which resulted in call&lt;int&gt; being instantiated and used. That doesn't depend on the name type_t being visible to the template at all though. For this, the compiler only really cares about the underlying type being referred to, not the name.

For that matter, the type we use to instantiate a template doesn't even need to have a name at all. For example:

template &lt;class T&gt;
void func(T t) { t(); }

struct foo {
    foo() {
        auto unknown_t = [] { std::cout &lt;&lt; &quot;lambda!\n&quot;; };

        func(unknown_t);
    }
};

A lambda expression basically creates a class that doesn't even have an actual name-but we can still use its type to instantiate a template just fine.

Summary

So, if we use a name, that name has to be visible where we use it (has been seen by the compiler, and is in scope). But the name of a type doesn't have to be visible for that type to be used to instantiate a template. In fact, the type doesn't need to have a name at all.

huangapple
  • 本文由 发表于 2023年7月14日 00:47:54
  • 转载请务必保留本文链接:https://go.coder-hub.com/76681681.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定