Search code examples
c++c++14variable-templates

Using a variable template inside inline constexpr function without exposing the variable template?


Is it possible to use a variable template inside an inline constexpr function without also exposing the variable template itself?

For example, this compiles and works:

template<typename T> constexpr T twelve_hundred = T(1200.0);

template<typename T>
inline constexpr T centsToOctaves(const T cents) {
    return cents / twelve_hundred<T>;
}

But this doesn't compile:

template<typename T>
inline constexpr T centsToOctaves(const T cents) {
    template<typename U> constexpr U twelve_hundred = U(1200.0);
    return cents / twelve_hundred<T>;
}

The reason seems to be that template declarations aren't allowed in block scope (GCC gives an informative error message about this, Clang doesn't).

To repeat the motivation in a bit more detail, the function is inline and defined in a header, and I'm not interested in exposing the variable template wherever the header is included.

I guess I can define a detail namespace and put the variable template there, but it would be nicer not to expose the variable template at all. Maybe it's not possible.


Solution

  • From the standard we have that:

    A template-declaration is a declaration. [...]. A declaration introduced by a template declaration of a variable is a variable template. [...]

    And:

    A template-declaration can appear only as a namespace scope or class scope declaration.

    Therefore no, it isn't allowed.
    You can still wrap it in a class and make both the data member and the member function static if you don't want to expose it:

    class C {
        template<typename T>
        static constexpr T twelve_hundred = T(1200.0);
    
    public:
        template<typename T>
        static constexpr T centsToOctaves(const T cents) {
            return cents / twelve_hundred<T>;
        }
    };
    
    int main() {
        C::centsToOctaves(42);
    }
    

    Another possible solution is:

    class C {
        template<typename T>
        static constexpr T twelve_hundred = T(1200.0);
    
        template<typename T>
        friend inline constexpr T centsToOctaves(const T cents);
    };
    
    template<typename T>
    inline constexpr T centsToOctaves(const T cents) {
        return cents / C::twelve_hundred<T>;
    }
    
    int main() {
        centsToOctaves(42);
    }
    

    It has the plus that centsToOctaves is no longer a member function of C, as mentioned in the comments.

    That being said, I don't understand what prevents you from simply doing this:

    template<typename T>
    inline constexpr T centsToOctaves(const T cents) {
        return cents / T{1200};
    }