天道酬勤,学无止境

当编译器可以推断类型时,对函数使用类型签名的充分理由是什么?(What is a good reason to use a type signature for functions when the compiler can infer the types)

问题

我正在尝试使用“良好的 Haskell 风格”进行编码,因此我正在尝试遵循我发现的典型编码标准。 此外,使用 -Wall 和 -Werror 进行编译,因为我在来自 C 时已经习惯了。我经常收到的警告之一是“没有类型签名的顶级绑定”,然后编译器会告诉我类型签名应该是什么。

我错过了明确定义类型签名的好处。 作为一个具体的例子:

-- matchStr :: String -> String ->  Maybe (String)
matchStr str s
        | isPrefixOf str s = Just(drop (length str) s)
        | otherwise = Nothing

现在,如果我想将类型从 String 更改为 ByteString 以提高性能,会发生什么? 我必须导入 ByteString 包并使用某些函数的限定版本。 无需其他更改。 如果我有类型签名,那么我也必须改变它,但 Haskell 编译器会注意到这个改变并正确推断新类型。

那么我错过了什么? 为什么在一般情况下将类型签名显式放在函数上被认为是一个好主意? 即我知道可能会有例外,因为它是一个好主意,但为什么它通常被认为是好的?

回答1

如果您在定义函数时犯了错误,编译器可能会推断出不是您期望的类型。 如果您声明了您期望的类型,编译器将报告函数定义中的错误。

如果没有声明,编译器就无法知道它的推断类型是“错误的”,它最终会在您尝试调用函数的地方报告错误,这使得问题的真正所在不太清楚。

如果调用函数没有类型声明任何话,而不是报告错误出现时,编译器可能只是推断不正确类型也为它们,导致调用函数的问题。 您最终会在某处收到一条错误消息,但它可能与问题的实际根源相去甚远。


此外,您可以声明比编译器推断的更具体的类型。 例如,如果您编写函数:

foo n = n + 1

编译器将推断类型Num a => a -> a ,这意味着它必须编译可以与任何Num实例一起使用的通用代码。 如果您将类型声明为Int -> Int ,编译器可能能够生成更高效的代码,这些代码专门用于整数。


最后,类型声明用作文档。 编译器可能能够推断复杂表达式的类型,但对于人类读者来说并不容易。 类型声明提供了“大图”,可以帮助程序员理解函数的作用。

请注意,Haddock 注释附加到声明,而不是定义。 编写类型声明是使用 Haddock 为函数提供附加文档的第一步。

回答2

我认为文档是具有显式类型签名的优势之一。

来自“类型和编程语言”:

类型在阅读程序时也很有用。 过程头和模块接口中的类型声明构成了一种文档形式,提供了有关行为的有用提示。 此外,与嵌入在注释中的描述不同,这种形式的文档不会过时,因为在每次运行编译器时都会对其进行检查。 类型的这种作用在模块签名中尤为重要。

回答3

有几个原因。

  1. 它可能使人类更容易阅读代码。 (OTOH,如果你有几十个微小的定义,有时类型签名会增加更多的混乱。)
  2. 如果您的实现是错误的,编译器可能会推断出错误的类型。 这可能会导致其他函数的类型被推断错误,最终导致与实际损坏的函数相距很远的类型错误。
  3. 出于性能原因,您可能希望为函数提供比它可能具有的多态性更少的类型。
  4. 您可能想要使用类型别名。 这允许您在多个位置快速更改类型,并记录值背后的一些意图。 (比较FilePathString 。)
  5. 编译器可以自动找出类型,但并非所有外部工具都可以做到这一点。 (例如,最初 Haddock 会拒绝为缺少显式类型签名的函数生成文档——尽管我认为现在已经修复了。)

值得注意的是,有些人主张从类型签名开始,然后再填写实现。

无论如何,大多数人似乎都建议为所有或大多数顶级声明使用类型签名。 您是否将它们用于局部变量/函数是一个品味问题。

回答4

您人为的示例确实是人为的,因为函数体不依赖于列表内容的类型。 在这种情况下,确实很难看出将类型定义为[String] -> ([String],[String])而不是[a]->([a],[a])什么好处

如果您尝试定义依赖于内容的函数,您将看到类型定义并不是您需要更改的唯一内容。 例如,更改MArray的列表将更加复杂,而不仅仅是使用在不同模块中碰巧具有相同名称的函数。 因此,在少数情况下在重构期间限定名称并不是不指定类型签名的充分理由。

指定类型会告诉编译器一些意图。 然后编译器将能够报告意图和实现的不匹配。

标签

受限制的 HTML

  • 允许的HTML标签:<a href hreflang> <em> <strong> <cite> <blockquote cite> <code> <ul type> <ol start type> <li> <dl> <dt> <dd> <h2 id> <h3 id> <h4 id> <h5 id> <h6 id>
  • 自动断行和分段。
  • 网页和电子邮件地址自动转换为链接。

相关推荐
  • 在诸如Stream.reduce()之类的API中选择不变性的充分理由是什么?(What are good reasons for choosing invariance in an API like Stream.reduce()?)
    问题 回顾Java 8 Stream API设计,我对Stream.reduce()参数的通用不变性感到惊讶: <U> U reduce(U identity, BiFunction<U,? super T,U> accumulator, BinaryOperator<U> combiner) 同一API的看似更通用的版本可能已对U各个引用应用了协方差/协方差,例如: <U> U reduce(U identity, BiFunction<? super U, ? super T, ? extends U> accumulator, BiFunction<? super U, ? super U, ? extends U> combiner) 目前,这将允许以下操作(不可能): // Assuming we want to reuse these tools all over the place: BiFunction<Number, Number, Double> numberAdder = (t, u) -> t.doubleValue() + u.doubleValue(); // This currently doesn't work, but would work with the suggestion Stream<Number> stream = Stream.of
  • Java的类型擦除有什么好处?(What are the benefits of Java's types erasure?)
    问题 我今天读了一条推文说: 当Java用户抱怨类型擦除时,这很有趣,这是Java正确的唯一事情,而忽略了所有错误的事情。 因此,我的问题是: Java的类型擦除是否有好处? 除了向后兼容和运行时性能的JVM实现首选项之外,它(可能)还提供了哪些技术或编程风格的好处? 回答1 类型擦除好 让我们坚持事实 到目前为止,很多答案都与Twitter用户有关。 集中精力于消息而不是使者很有帮助。 到目前为止,即使是摘录中也有一个相当一致的信息: 当Java用户抱怨类型擦除时,这很有趣,这是Java正确的唯一事情,而忽略了所有错误的事情。 我得到了巨大的收益(例如参数性)和零成本(指定成本是想象力的极限)。 new T是一个损坏的程序。 与“所有命题都是正确的”的主张是同构的。 我不大喜欢这个。 目标:合理的程序 这些推文反映了一个观点,该观点对我们是否可以使机器执行某项操作不感兴趣,而更多地是我们是否可以推断该机器将执行我们实际想要的操作。 充分的推理是一个证明。 可以用正式符号或不那么正式的形式指定证明。 不论规范语言如何,它们都必须清晰且严格。 非正式规范并非不可能正确构建,但在实际编程中经常存在缺陷。 我们最终以自动化和探索性测试之类的补救措施来弥补非正式推理中存在的问题。 这并不是说测试本质上是一个坏主意,但是引用的Twitter用户暗示这是更好的方法。 因此
  • [TypeScript] 编程实践之1: Google的TypeScript代码风格1:介绍
    TypeScript语言规范 1 介绍1.1 环境声明1.2 函数类型1.3 对象类型1.4 结构子类型化1.5 上下文类型推断1.6 类1.7 枚举类型1.8 字符串参数重载1.9 通用类型和功能1.10 命名空间1.11 模块 版本1.8 2016年1月 Microsoft自2012年10月1日起根据Open Web Foundation最终规范协议版本1.0(“ OWF 1.0”)提供此规范。OWF 1.0可以从http://www.openwebfoundation.org/legal/the-owf-1-0-agreements/owfa-1-0获得。 TypeScript是Microsoft Corporation的注册商标。 1 介绍 诸如Web电子邮件,地图,文档编辑和协作工具之类的JavaScript应用程序已成为日常计算中越来越重要的部分。我们设计TypeScript来满足构建和维护大型JavaScript程序的JavaScript编程团队的需求。 TypeScript帮助编程团队定义软件组件之间的接口,并深入了解现有JavaScript库的行为。 TypeScript还使团队可以通过将其代码组织到可动态加载的模块中来减少命名冲突。 TypeScript的可选类型系统使JavaScript程序员能够使用高效的开发工具和实践:静态检查,基于符号的导航
  • 类型的Haskell函数:IO字符串->字符串(A Haskell function of type: IO String-> String)
    问题 我在Haskell中编写了一堆代码来创建文本索引。 顶部函数如下所示: index :: String -> [(String, [Integer])] index a = [...] 现在,我想给这个函数一个从文件中读取的字符串: index readFile "input.txt" 这将不起作用,因为readFile的类型为FilePath-> IO String。 无法将预期的类型“字符串”与推断的类型“ IO字符串”匹配 我看到错误,但是找不到任何类型的函数: IO String -> String 我想成功的关键在于某些Monad,但我找不到解决问题的方法。 回答1 您可以轻松地编写一个调用readFile操作并将其结果传递给索引函数的函数。 readAndIndex fileName = do text <- readFile fileName return $ index text 但是,IO monad会污染使用它的所有东西,因此此函数的类型为: readAndIndex :: FilePath -> IO [(String, [Integer])] 回答2 没有此功能的理由非常充分。 Haskell具有功能纯净的概念。 这意味着,使用相同的参数调用时,函数将始终返回相同的结果。 唯一允许IO的地方是IO monad内部。 如果有*功能 index :: IO
  • 什么是单态性限制?(What is the monomorphism restriction?)
    问题 我对haskell编译器有时会推断出比我所期望的少多态的类型感到困惑,例如在使用无点定义时。 看来问题出在“单态限制”,在较早版本的编译器上默认情况下处于启用状态。 考虑以下haskell程序: {-# LANGUAGE MonomorphismRestriction #-} import Data.List(sortBy) plus = (+) plus' x = (+ x) sort = sortBy compare main = do print $ plus' 1.0 2.0 print $ plus 1.0 2.0 print $ sort [3, 1, 2] 如果我用ghc编译,则不会得到错误,可执行文件的输出为: 3.0 3.0 [1,2,3] 如果我将main更改为: main = do print $ plus' 1.0 2.0 print $ plus (1 :: Int) 2 print $ sort [3, 1, 2] 我没有编译时错误,输出变为: 3.0 3 [1,2,3] 如预期的那样。 但是,如果我尝试将其更改为: main = do print $ plus' 1.0 2.0 print $ plus (1 :: Int) 2 print $ plus 1.0 2.0 print $ sort [3, 1, 2] 我收到类型错误: test
  • 为什么不能将匿名方法分配给var?(Why can't an anonymous method be assigned to var?)
    问题 我有以下代码: Func<string, bool> comparer = delegate(string value) { return value != "0"; }; 但是,以下内容无法编译: var comparer = delegate(string value) { return value != "0"; }; 为什么编译器不能弄清楚它是Func<string, bool> ? 它采用一个字符串参数,并返回一个布尔值。 相反,它给了我错误: 无法将匿名方法分配给隐式类型的局部变量。 我有一个猜测,那就是如果var版本已编译,如果我具有以下内容,它将缺乏一致性: var comparer = delegate(string arg1, string arg2, string arg3, string arg4, string arg5) { return false; }; 由于Func <>最多只允许4个参数(在.NET 3.5中,这是我正在使用的参数),因此上述内容没有任何意义。 也许有人可以澄清这个问题。 谢谢。 回答1 其他人已经指出,您可能意味着无限多种委托类型。 Func有什么特别之处,以至于它应该是默认值,而不是Predicate或Action或任何其他可能性? 而且,对于lambda而言,为什么很明显的目的是选择委托形式而不是表达式树形式?
  • Java 8中异常类型推断的独特功能(A peculiar feature of exception type inference in Java 8)
    问题 在此站点上为另一个答案编写代码时,我遇到了这种特殊性: static void testSneaky() { final Exception e = new Exception(); sneakyThrow(e); //no problems here nonSneakyThrow(e); //ERRROR: Unhandled exception: java.lang.Exception } @SuppressWarnings("unchecked") static <T extends Throwable> void sneakyThrow(Throwable t) throws T { throw (T) t; } static <T extends Throwable> void nonSneakyThrow(T t) throws T { throw t; } 首先,我很困惑,为什么sneakyThrow调用对于编译器是可以的。 当在任何地方都没有提及未经检查的异常类型时,它为T推断出什么可能的类型? 其次,接受这一工作原理后,为什么编译器会在nonSneakyThrow调用中抱怨? 他们看起来非常相似。 回答1 sneakyThrow的T的推断为RuntimeException 。 可以从有关类型推断的语言规范(http://docs.oracle.com
  • 新的数组放置是否需要缓冲区中未指定的开销?(Array placement-new requires unspecified overhead in the buffer?)
    问题 C ++ 2月11日草案的5.3.4 [expr.new]给出了示例: new(2,f) T[5]导致对operator new[](sizeof(T)*5+y,2,f)的调用。 此处,x和y是表示数组分配开销的非负未指定值; new-expression的结果将从operator new[]返回的值中偏移此量。 此开销可能会应用于所有数组new-expression ,包括引用库函数operator new[](std::size_t, void*)和其他布局分配函数的那些表达式。 开销量可能从一次新调用到另一次调用而有所不同。 —结束示例] 现在,使用以下示例代码: void* buffer = malloc(sizeof(std::string) * 10); std::string* p = ::new (buffer) std::string[10]; 根据上面的引用,第二行new (buffer) std::string[10]将在内部调用operator new[](sizeof(std::string) * 10 + y, buffer) (在构造单个std::string之前operator new[](sizeof(std::string) * 10 + y, buffer) std::string对象)。 问题是,如果y > 0 ,则预分配的缓冲区将太小
  • 不允许在 Java 方法覆盖上使用超类型的原因是什么?(What is the reasoning behind not allowing supertypes on Java method overrides?)
    问题 编译器认为以下代码无效: class Foo { void foo(String foo) { ... } } class Bar extends Foo { @Override void foo(Object foo) { ... } } 我认为这在 JLS 8.4.8.1 中有描述:“m1 的签名是 m2 签名的子签名(第 8.4.2 节)。” 并且在 8.4.2 中:“对应类型变量的边界是相同的”。 我的问题是:为什么子类型(Bar)中的参数不能是超类型(Foo)中参数的超类型。 在示例中 Object 是 String 的超类型。 据我所知,允许这不会违反 Liskov 替换原则。 是否存在允许这样做会破坏代码的情况,还是当前 JLS 的限制? 回答1 (重写,从不同的角度......我原来的答案包含一个错误。:() 为什么子类型(Bar)中的参数不能是超类型(Foo)中参数的超类型。 我相信从技术上讲它可以,并且不会在类型替换(Liskov 替换原则)之后破坏祖先契约。 类型是按值传递的(包括引用类型)。 永远不能强迫调用者处理与传入的参数类型不同的参数类型。 方法体可以交换参数类型,但不能将其返回给调用者(没有“输出参数类型”这样的东西)。 如果您的提议被允许,并且对祖先方法签名进行了调用,则后代类可以使用更广泛的类型覆盖该方法
  • 刚性变量数组(Arrays with rigid variable)
    问题 好吧,我正在做一个问题,我使用的函数有一个刚性变量。 我有一个使用数组解决这个问题的想法。 因此,我想到了使用与我创建的函数具有相同刚性变量的数组,但是我不知道如何制作具有刚性变量的数组。 我尝试了以下操作,但没有效果: rearrange :: [Int] -> [a] -> [a] rearrange l la = elems (f 1 posarr) where b = length l listarr :: Array Int Int listarr = listArray (1,b) l arra :: Array Int c arra = listArray (1,b) la posarr :: Array Int c posarr = listArray (1,b) la f i posarr | (b < i) = posarr | otherwise = f (i+1) (posarr // [(listarr!i,arra!i)] ) 我得到的错误是刚性变量。 有什么可能的解决办法? 请在我使用的以下函数中给出如何使用刚性变量制作数组的想法 回答1 您遇到了麻烦,因为您写出的“内部”类型签名并不意味着它们看起来像是真正的含义。 特别是,您对c的使用与顶部签名中的a不相互对应,而必须相互对应。 Haskell是抱怨这些“严格定义的”类型变量,尽管是变量
  • 函数指针类型如何不安全(How are function pointers type unsafe)
    问题 首先,类型安全意味着如果操作不当,编译器可以立即捕获任何内容。 现在,我听说函数指针不是类型安全的,但是每当我试图错误地使用它们时,编译器都会为我报告错误。 那么,它是如何输入 unsafe 的呢? 例如,这是一个接受函数指针的函数原型 void SortElements(void* MyArray, unsigned int iNumofElems,size_t size, int(*compare_funct)(void* First,void* SecondElem)) 我定义了几个传递给它的函数: int MySortAsc(void* First, void* Second); void MyFunct2(); void MyFunct3(void* First); 代码仅编译为: SortElements(MyArray, 10, sizeof(DataType), &MySortAsc); //Compiles SortElements(MyArray, 10, sizeof(DataType), &MyFunct2); //Fails 知道如何在这里误用函数指针吗? 是不是因为这个: void (*functionPointer)(); ... int integer = 0xFFFFFFFF; functionPointer = (void(*)()
  • 为什么不从构造函数推断模板参数?(Why not infer template parameter from constructor?)
    问题 我今天的问题很简单:为什么编译器不能像从函数参数中那样从类构造函数中推断出模板参数? 例如,为什么以下代码无效: template <typename obj> class Variable { obj data; public: Variable(obj d) { data = d; } }; int main() { int num = 2; Variable var(num); // would be equivalent to Variable<int> var(num), return 0; // but actually a compile error } 正如我所说,我知道这是无效的,所以我的问题是为什么呢? 允许这样做会造成任何重大的语法漏洞吗? 是否存在一个实例,该实例不希望使用此功能(在推断类型会导致问题的情况下)? 我只是想了解允许对函数进行模板推断的逻辑,而对于适当构造的类则不行。 回答1 我认为这是无效的,因为构造函数并非始终是类的唯一入口(我在谈论复制构造函数和operator =)。 因此,假设您正在使用这样的类: MyClass m(string s); MyClass *pm; *pm = m; 我不确定解析器是否知道MyClass pm是什么模板类型是否很明显。 不知道我说的话是否有意义,但随时可以添加一些评论,这是一个有趣的问题。 C +
  • 为什么在Rust中需要显式的生存期?(Why are explicit lifetimes needed in Rust?)
    问题 我正在阅读Rust的一生一章,并且遇到了这个示例,它给出了一个有名的/显式的生命周期: struct Foo<'a> { x: &'a i32, } fn main() { let x; // -+ x goes into scope // | { // | let y = &5; // ---+ y goes into scope let f = Foo { x: y }; // ---+ f goes into scope x = &f.x; // | | error here } // ---+ f and y go out of scope // | println!("{}", x); // | } // -+ x goes out of scope 对我来说很清楚,编译器防止的错误是分配给x的引用的after-after-free :在完成内部作用域之后, f以及因此&f.x变得无效,并且不应该将其分配给x 。 我的问题是,可以不使用显式的'a生存期'a就可以轻松地分析问题,例如通过推断对更大范围的引用的非法分配( x = &f.x; )。 在哪些情况下,真正需要显式生存期来防止“用后使用”(或其他某些类?)错误? 回答1 其他答案都具有重点(fjh的具体示例需要显式生存期),但是却缺少一件事:当编译器告诉您错误时,为什么需要显式生存期? 这实际上与
  • 打字稿:类型中缺少索引签名(Typescript: Index signature is missing in type)
    问题 我希望MyInterface.dic像一个字典name: value ,我将其定义如下: interface MyInterface { dic: { [name: string]: number } } 现在,我创建一个等待我的类型的函数: function foo(a: MyInterface) { ... } 和输入: let o = { dic: { 'a': 3, 'b': 5 } } 我期望foo(o)是正确的,但是编译器正在下降: foo(o) // Typescript error: Index signature is missing in type { 'a': number, 'b': number } 我知道有可能的强制转换: let o: MyInterface = { ... }可以解决问题,但问题是,为什么打字稿无法识别我的打字? 额外:如果将o声明为内联,则可以正常工作: foo({ dic: { 'a': 3, 'b': 5 } }) 回答1 问题在于,当推断类型时, o的类型为: { dic: { a: number, b: number } } 这与{ dic: { [name: string]: number } } 。 至关重要的是,不允许使用最高签名执行o.dic['x'] = 1 。 有了第二个签名,您就是。
  • C#3.0泛型类型推断-将委托作为函数参数传递(C# 3.0 generic type inference - passing a delegate as a function parameter)
    问题 我想知道为什么当C#3.0编译器可以隐式为同一方法创建委托时,将其作为参数传递给泛型函数时,为什么无法推断方法的类型。 这是一个例子: class Test { static void foo(int x) { } static void bar<T>(Action<T> f) { } static void test() { Action<int> f = foo; // I can do this bar(f); // and then do this bar(foo); // but this does not work } } 我以为我可以将foo传递给bar并让编译器从传递的函数签名中推断Action<T>的类型,但这不起作用。 但是我可以从foo创建一个Action<int>而不进行强制转换,所以编译器不能通过类型推断也不能做同样的事情吗? 回答1 也许这样可以使它更清晰: public class SomeClass { static void foo(int x) { } static void foo(string s) { } static void bar<T>(Action<T> f){} static void barz(Action<int> f) { } static void test() { Action<int> f = foo; bar
  • 什么是TypeScript?为什么我要用它代替JavaScript? [关闭](What is TypeScript and why would I use it in place of JavaScript? [closed])
    问题 关门了。 这个问题需要更加集中。 它当前不接受答案。 想要改善这个问题吗? 更新问题,使其仅通过编辑此帖子即可将重点放在一个问题上。 4年前关闭。 改善这个问题 您能描述一下TypeScript语言是什么吗? JavaScript或可用的库无法执行的工作是什么,这使我有理由考虑它? 回答1 我最初是在TypeScript仍然热销时写这个答案的。 五年后,这是一个不错的概述,但请查看下面的Lodewijk的答案以更深入地了解 1000英尺查看... TypeScript是JavaScript的超集,主要提供可选的静态类型,类和接口。 最大的好处之一就是使IDE能够提供一个更丰富的环境,以便您在键入代码时发现常见错误。 要了解我的意思,请观看有关该语言的Microsoft入门视频。 对于大型JavaScript项目,采用TypeScript可能会导致软件更强大,同时仍可以在运行常规JavaScript应用程序的地方进行部署。 它是开源的,但是如果您使用受支持的IDE,则只有在键入时才获得聪明的Intellisense。 最初,这只是Microsoft的Visual Studio(也在Miguel de Icaza的博客文章中提到)。 如今,其他IDE也提供TypeScript支持。 还有其他类似的技术吗? 有CoffeeScript,但这确实有不同的用途。 恕我直¨
  • main()方法在C语言中如何工作?(How does the main() method work in C?)
    问题 我知道有两种不同的签名可以编写main方法- int main() { //Code } 或为处理命令行参数,我们将其写为- int main(int argc, char * argv[]) { //code } 在C++我知道我们可以重载一个方法,但是在C中,编译器如何处理main函数的这两个不同的签名? 回答1 C语言的某些功能起初只是一些hack,而这些hack恰好起作用了。 主要功能以及可变长度参数列表的多个签名是这些功能之一。 程序员注意到,他们可以将额外的参数传递给函数,并且使用给定的编译器也不会发生任何不良情况。 如果调用约定如下: 调用函数清除参数。 最左边的参数更靠近堆栈的顶部或堆栈框架的底部,因此伪参数不会使寻址无效。 遵循这些规则的一组调用约定是基于堆栈的参数传递,由此调用者弹出参数,并将它们从右向左推送: ;; pseudo-assembly-language ;; main(argc, argv, envp); call push envp ;; rightmost argument push argv ;; push argc ;; leftmost argument ends up on top of stack call main pop ;; caller cleans up pop pop 在这种调用约定是这种情况的编译器中
  • 如何正确传递参数?(How to pass parameters correctly?)
    问题 我是C ++初学者,但不是编程初学者。 我正在尝试学习C ++(c ++ 11),对我来说最重要的事情还不清楚:传递参数。 我考虑了以下简单示例: 一个具有所有成员原始类型的类: CreditCard(std::string number, int expMonth, int expYear,int pin):number(number), expMonth(expMonth), expYear(expYear), pin(pin) 一个具有基本类型+ 1个复杂类型作为成员的类: Account(std::string number, float amount, CreditCard creditCard) : number(number), amount(amount), creditCard(creditCard) 一个具有基本类型+ 1个复杂类型集合的成员的类: Client(std::string firstName, std::string lastName, std::vector<Account> accounts):firstName(firstName), lastName(lastName), accounts(accounts) 创建帐户时,请执行以下操作: CreditCard cc("12345",2,2015,1001); Account acc(
  • 具有未推断上下文的函数模板的部分排序(Partial ordering with function template having undeduced context)
    问题 在阅读另一个问题时,我遇到了部分排序的问题,我将其缩减为以下测试用例 template<typename T> struct Const { typedef void type; }; template<typename T> void f(T, typename Const<T>::type*) { cout << "Const"; } // T1 template<typename T> void f(T, void*) { cout << "void*"; } // T2 int main() { // GCC chokes on f(0, 0) (not being able to match against T1) void *p = 0; f(0, p); } 对于这两个函数模板,进入重载解析的特化函数类型是void(int, void*) 。 但是偏序(根据 comeau 和 GCC)现在说第二个模板更专业。 但为什么? 让我通过部分排序并显示我有问题的地方。 可能Q是一个独特的组合类型,用于根据14.5.5.2确定偏序。 T1转换参数列表(插入 Q): (Q, typename Const<Q>::type*) 。 参数类型为AT = (Q, void*) T2转换参数列表(插入 Q): BT = (Q, void*) ,这也是参数的类型。 T1非转换参数列表
  • 为什么不可能在通用静态类中声明扩展方法?(Why is it impossible to declare extension methods in a generic static class?)
    问题 我想为某些通用类创建很多扩展方法,例如 public class SimpleLinkedList<T> where T:IComparable 我已经开始创建这样的方法: public static class LinkedListExtensions { public static T[] ToArray<T>(this SimpleLinkedList<T> simpleLinkedList) where T:IComparable { //// code } } 但是当我试图使LinkedListExtensions类像这样通用时: public static class LinkedListExtensions<T> where T:IComparable { public static T[] ToArray(this SimpleLinkedList<T> simpleLinkedList) { ////code } } 我得到“扩展方法只能在非泛型,非嵌套的静态类中声明”。 我正在尝试猜测此限制来自何处,并且没有任何想法。 编辑:仍然没有清楚的问题的愿景。 似乎由于某种原因而没有实施。 回答1 一般而言,由于在使用扩展方法时未指定类,因此编译器将无法知道定义扩展方法的类是哪个: static class GenStatic<T> { static void