0

I want to declare a function, which will take as a parameter a variable (let's say, int), which should be parametrized by a class. Speaking in terms of lambda calculus, I want my parameter to have a kind * -> int.

Example of a function I want to be able to write (Spec is the variable):

template <??? Specification, typename T>
auto make_array() {
    return std::array<T, Specification<T>>;
}

Since C++14 we have variable templates, so we can do something like this:

template <typename T>
constexpr int digits = std::numeric_limits<T>::digits;

The problem is, how do I pass that into a function? In the notes section of cppreference it is stated that

Variable templates cannot be used as template template arguments.

But does that mean that there is actually no way to pass parametrized variable as a function parameter? What you can do is, for example, create a class which has a static field denoting value, but an obvious drawback is that the users of my function must derive from that class.

I believe there might be some workaround using SFINAE, but I lack skills in that area.

  • wording is a bit confusing because you are talking about a function parameter but code looks like you want a template parameter also the quote is about tempalte parameter – 463035818_is_not_an_ai Nov 25 '20 at 21:13
  • 1
    Does this answer your question? [What are some uses of template template parameters?](https://stackoverflow.com/questions/213761/what-are-some-uses-of-template-template-parameters) – Sprite Nov 25 '20 at 21:14
  • @idclev463035818 I am curious about any way to implement such a functionality, but I guess it can be considered a template parameter. – Nikita Golikov Nov 25 '20 at 21:20
  • @Sprite Along with the provided answer, it does answer my question. – Nikita Golikov Nov 25 '20 at 21:36
  • your example uses `int` because you want to pass the "value of `specification`" as template parameter to `std::array`, right? Note that the parameter of `std::array` is `size_t` not `int` (I first made the same mistake in my answer) – 463035818_is_not_an_ai Nov 25 '20 at 21:43

2 Answers2

4

Unless you insist on using a variable template, you can use a type trait:

template <typename T> struct Specification;

you can specialize it for example for int:

template <>
struct Specification<int> {
    static constexpr size_t value = 42;
};

and as you want to have different Specifications, pass it as template template parameter:

template <template<class> class S, typename T>
auto make_array() {
    return std::array<T, S<T>::value>{};
}

Complete example:

#include <array>
#include <cstddef>

template <template<class> class S, typename T>
auto make_array() {
    return std::array<T, S<T>::value>{};
}

template <typename T> struct Specification;

template <>
struct Specification<int> {
    static constexpr size_t value = 42;
};

int main(){
  auto x = make_array<Specification,int>();  
}

Note that I was rather verbose for the sake of clarity. You can save a bit of typing by using std::integral_constant, eg:

template <>
struct Specification<double> : std::integral_constant<size_t,3> {};
463035818_is_not_an_ai
  • 109,796
  • 11
  • 89
  • 185
  • Thank you! This solution allows me to use multiple specifications, and that is exactly what I was looking for. – Nikita Golikov Nov 25 '20 at 21:28
  • Note you don't necessarily need specializations here, either: in this example using `template struct digits : std::integral_constant::digits> {};` instead of a variable template would allow you to use `digits` exactly as you wanted to anyway. – N. Shead Nov 25 '20 at 21:34
  • @N.Shead OP wants different specifications. With your solution, how would you pass different variants of `digits` to `make_array`? Anyhow I think your comment adresses the wrong user, isnt this for OP? If you have a better solution, please post an answer. I don't understand yet how your code in the comment answers the quesiton – 463035818_is_not_an_ai Nov 25 '20 at 21:36
  • @idclev463035818 yes, that was meant to be for OP. But my assumption is that they would choose between `digits` or `size : std::integral_constant {}` or so forth: different specifications as "variable templates" rather than specifications as "results for a given T" – N. Shead Nov 25 '20 at 21:37
  • @N.Shead sorry I cannot follow you. Would be nice if you expanded this to an answer – 463035818_is_not_an_ai Nov 25 '20 at 21:38
  • @idclev463035818 expanded in an answer; hopefully that makes my point clearer! Apologies for the confusion. – N. Shead Nov 25 '20 at 21:43
  • Been doing this for decades and template syntax still gives me a headache. – 3Dave Nov 25 '20 at 21:54
  • 1
    @3Dave I am doing this since not so long, SFINAE still causes me headaces and most modern stuff that came with C++20 is worse than headaces. It almost feels like once you got the hang of it there is something radically different to do the same ;) – 463035818_is_not_an_ai Nov 25 '20 at 22:03
  • @idclev463035818 "This is the way." :) – 3Dave Nov 26 '20 at 00:17
1

As an follow-up on idclev's answer, you can avoid the need to explicitly specialise for the different types for individual "specifications" just by inheriting from e.g. integral_constant. For example, your desired usage was something like

template <template <typename T> int Specification, typename T>
auto make_array() {
    return std::array<T, Specification<T>>;
}

template <typename T>
constexpr int digits = std::numeric_limits<T>::digits;

// ...

auto foo = make_array<digits, double>();

However, as you have noted, this is impossible: you cannot pass variable templates as template-template parameters. However, by turning digits into a structure directly you can do this:

template <template <typename T> class Specification, typename T>
auto make_array() {
    // we no longer have the guarantee of the type here, unfortunately
    // but you can use a static_assert to improve error messages
    // (also using `std::size_t` here for correctness)
    static_assert(
        std::is_convertible<decltype(Specification<T>::value), std::size_t>::value,
        "value must be convertible to size_t");
    
    return std::array<T, Specification<T>::value>;
}

// use a type rather than a variable template
// we just inherit from integral constant to save on some typing
// (though you could do this explicitly as well)
template <typename T>
struct digits : std::integral_constant<int, std::numeric_limits<T>::digits> {};

// ...

// same call!
auto foo = make_array<digits, double>();
463035818_is_not_an_ai
  • 109,796
  • 11
  • 89
  • 185
N. Shead
  • 3,828
  • 1
  • 16
  • 22
  • you assume that OP always wants `std::numeric_limits::digits` if that would be the case, you need not pass anything to `make_array` in addition to `T`, you could use `std::numeric_limits::digits` directly inside `make_array`. I suppose the part about `std::numeric_limits::digits` in the question was just a random example for a variable template – 463035818_is_not_an_ai Nov 25 '20 at 21:45
  • @idclev463035818 I don't assume that, though? They could just as well do `struct size : std::integral_constant {};` instead and call `make_array();` — there's nothing stopping them? – N. Shead Nov 25 '20 at 21:46
  • ok, sorry I was confused. But still your answer is basically the same as mine. Only if one of the desired specifications *is* `std::numerical_limits::digits` you need not specialize "manually". Otherwise I dont see the difference – 463035818_is_not_an_ai Nov 25 '20 at 21:48
  • @idclev463035818 Yes, it is. That's why I just left it as a comment :) – N. Shead Nov 25 '20 at 21:49
  • 1
    ok sorry, for being a bit stubborn ;). Code in comments is really hard to understand... – 463035818_is_not_an_ai Nov 25 '20 at 21:49
  • @idclev463035818 just one last thing: they would only need to specialise, ever, if their original variable templates that they were trying to use were specialised. As in, specialisation is the "extension" to the general rule of using variable templates. Otherwise why would they not just be doing `make_array<3, int>();` or whatever directly? – N. Shead Nov 25 '20 at 21:54
  • what is above and below depends on each users sorting. In my view there is no answer above yours. Also user names can change, so better use a link – 463035818_is_not_an_ai Nov 25 '20 at 22:00