4

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.

Danra
  • 9,546
  • 5
  • 59
  • 117
  • Think the best you can do is to make it a private (static) member of a class, which friends your free function. That said, I don't consider the repetition and boilerplate worth it over just putting it in a detail namespace. This is a very well worn convention, and C++ is not really the kind of language where you can make bad behavior impossible anyway. – Nir Friedman Feb 15 '17 at 17:52

2 Answers2

3

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};
}
skypjack
  • 49,335
  • 19
  • 95
  • 187
  • Yes, I wrote this above. The question was if there is a way to not expose the variable template. – Danra Feb 15 '17 at 17:48
  • As I wrote on the other answer, should not assume it's ok to make a free function a class static function instead, this has pretty wide implications. – Nir Friedman Feb 15 '17 at 17:50
  • @NirFriedman Your comment is about inlining the function and it seems to me it was the expectation of the OP. Anyway, make it a friend can be a workaround. Let me update the answer. – skypjack Feb 15 '17 at 17:52
  • Sorry, you're right, it's because I started writing a comment and then they changed to forwarding and I changed my comment. That said, the substance still holds: a free function has quite different behavior from a static function that cannot be reconciled. – Nir Friedman Feb 15 '17 at 17:55
  • Upvoted since I think this is the best possible answer that can meet the original requirements. – Nir Friedman Feb 15 '17 at 18:00
  • @skypjack "That being said, I don't understand what prevents you from simply doing this" I was going to write Clang give you a warning on narrowing double to float, since that's what I've seen when I tried that, but I can't see it anymore. I'll update if I see it again, maybe it was just my fluke. – Danra Feb 15 '17 at 18:15
  • @Danra It will be back using `T{1200.0}` and using `int` as a `T`, for example. Let me update the answer with a slightly different version that should work for both the cases. – skypjack Feb 15 '17 at 18:17
  • @skypjack Hmm, isn't there a risk of loss of precision when initializing a `double` (in case of `T`=`double`) from an `int` this way? – Danra Feb 15 '17 at 18:22
  • @Danra 1200 is perfectly representable as a `double` and `double{1200}` is just fine, as well as `int{1200}`. – skypjack Feb 15 '17 at 18:29
  • @skypjack Of course, I meant the more general issue of initializing a double with an int this way. – Danra Feb 15 '17 at 18:33
0

Aside from using a namespace, you can also put the template variable into a class, and declare it as private. Declaring template in function scope is not allowed.

class Detail {
 public:
  template<typename T>
  static constexpr T centsToOctaves(const T cents) {
    return cents / twelve_hundred<T>;
  }

 private:
  template<typename U>
  static constexpr U twelve_hundred = U(1200.0);
};

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

int main() {
  centsToOctaves<int>(12);
  return 0;
}

Unrelated:

You may not need to declare template constexpr variable. Since you can't modify it after initialisation, an alternative implementation can be using a literal directly:

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

And when you need to explicitly specialise the template variable, you can specialise the function template instead.

template <>
inline constexpr int centsToOctaves(const int cents) {
    using U = int;
    return cents / U(1200.0);
}

But sadly this solution will generate some duplicated code, may be a worse one.

felix
  • 2,213
  • 7
  • 16
  • The inline on the class static function is implied and redundant. Also, not sure why you would forward instead of just declaring a friend? – Nir Friedman Feb 15 '17 at 17:48
  • Indeed this is a solution, but not a great one unfortunately in the case that embedding the function in a class would be cumbersome (as in my case). – Danra Feb 15 '17 at 17:50
  • @NirFriedman I declare the member function first, and then I realise that the `centsToOctaves` was a non member function, thus, in that moment, I thought that using a member function to access private member variable is better. Then with out any further thinking... you know what has happened. – felix Feb 15 '17 at 18:55