Search code examples
c++templateslanguage-lawyerdecltype

nontype template parameter produced with decltype for function


I want to replace writing function signature with decltype, and found out it does not compile with most compilers. Is it a new feature or maybe unspecified behaviour?

#include <iostream>

template <typename T, T nontype>
struct CL {
    void call() { nontype(123); }
};

void f(int n) {
    std::cout << n << std::endl;
}
CL<void(*)(int), f> cl7;

using df = decltype(f);
CL<df, f> cl8; // << error

int main() {
    cl8.call();
}

so (not really sure about compiler versions):

clang - 3.4 http://rextester.com/UOIV91915

compiles, runs, produces output

g++ 4.9+ http://coliru.stacked-crooked.com/a/dbec1e202c48fd81

main.cpp:14:9: error: 'void(int)' is not a valid type for a template non-type parameter
 CL<df, f> cl8; // << error
         ^
main.cpp:14:14: error: invalid type in declaration before ';' token
 CL<df, f> cl8; // << error
              ^
main.cpp: In function 'int main()':
main.cpp:17:6: error: request for member 'call' in 'cl8', which is of non-class type 'int'
  cl8.call();

Visual Studio 2013 - update 4

fatal error C1001: An internal error has occurred in the compiler.

Solution

  • Non-type template parameters cannot have function type. They can have pointer to function type, and there is a paragraph in the standard that somehow implies that your code is correct - [temp.param]/8:

    A non-type template-parameter of type “array of T” or “function returning T” is adjusted to be of type “pointer to T” or “pointer to function returning T”, respectively.

    However, it is not clear whether this is done after template argument substitution or before it, which is covered in this defect report. An easy fix is to simply write

    using df = decltype(&f);
    

    Demo.


    Why does using df = decltype((f)); work?

    [dcl.type.simple]/4:

    For an expression e, the type denoted by decltype(e) is defined as follows:

    • if e is an unparenthesized id-expression or an unparenthesized class member access (5.2.5), decltype(e) is the type of the entity named by e. If there is no such entity, or if e names a set of overloaded functions, the program is ill-formed;
    • otherwise, if e is an xvalue, decltype(e) is T&&, where T is the type of e;
    • otherwise, if e is an lvalue, decltype(e) is T&, where T is the type of e;
    • otherwise, decltype(e) is the type of e.

    (f) is parenthesized and an lvalue, thus decltype((f)) is an lvalue reference to f's function type - void(&)(int). Template parameters can have reference to function type, thus it works. However, as this fact is quite counterintuitive (and not so well known), decltype(&f) should be less irritative in your code.