Question Details

No question body available.

Tags

c++ static-typing

Answers (4)

Accepted Answer Available
Accepted Answer
October 6, 2025 Score: 11 Rep: 221,249 Quality: Expert Completeness: 100%

Initially, the question puzzled me a bit. At a first glance, it looks like the auto keyword just replaces the explicit types in the first version of maxValue - and when we take the code just as a block of text, that's literally true.

However, what really happens here is that the auto keyword in the second version makes the function an abbreviated function template, which is a special template syntax introduced in C++20. Hence, the non-auto version of the second maxValue function isn't the first version, the equivalent non-auto version is something along the lines of

 template  
 static R maxValue(const T& map, const R min) {
     R max = min;
     // ...
     return max;
 }

(which, btw, works not just in C++20, but in C++11 or earlier, probably C++98, as Basilevs mentioned in a comment).

So if I interpret the question correctly, there are two different questions hidden inside your post:

1. When should we implement an utility function non generically, using explicit types, and when should we implement it generically, as a template?

2. Does C++20 abbreviated function template syntax lead to overuse of auto, and could it be beneficial to implement function templates with explicit template parameters (even in C++20)?

For the first question, the first principle which comes into my mind here is the YAGNI principle. An implementation which requires more effort to implement needs to be justified by some real requirement, otherwise "you ain't gonna need it (yet)". So without having the real requirement to implement maxValue as a template, with more than one type for the parameters map and min, I would usually hesitate to create a template. On the other hand, when you see a real opportunity to reuse maxValue with different types, a generic version is most probably justified.

However, with the newer C++20 syntax, writing a generic function seems sometimes to be less effort than writing a non-generic function. At least, one will have to type less characters. For very simple functions like maxValue the effort of testing / debugging does not differ much between the generic and the non-generic version. So when you are under the impression that in your specific case it makes things simpler, go ahead. Still, I would be careful about the well-known issues with templates:

  • cryptic compiler error messages during template instantiation

  • possibly more testing and debugging effort when the function is not that simple than in this question

  • possible obstacles when one needs to extend such an utility function later, in a way which preserves the genericity

How much these issues apply depends definitely on the specific situation, the specific C++ compiler, the complexity of the function in stake, the different types for which the template is going to be instantiated, and the expected frequency of changes to this function. This means, even when auto pretends to make templates function simple, it is still a trade-off, with no good "one-size-fits-all" rule.

My personal rule-of-thumb recommendation is:

  • when you see reusage potential with a template, use a template

  • when you have only one sensual type for each template parameter, my "default mode" would be to start with a non-generic version, even when that means a few keystrokes more.

For the second question, I would say the C++20 syntax is nice for certain cases, and parts of the arguments in the answers to this older SWE question, though not about templates, can be applied here as well. Still, there is one obvious drawback:

When the return type of the function must match a given parameter type, or types of certain parameters must match, the C++20 auto syntax obscures this.

For instance, in the maxValue function, the type of min must match the return type of maxValue. In the version I scetched above (with explicit template parameters R and T), this is clear from the function's signature. In the auto version, one has to look into the implementation of the function, see that the return type of maxValue is the same as max, (last line of the function), deduce that the type of max is the same as min (first line of the function), and conclude the return type of maxValue is the same as the type of min. Think about this when the function gets longer and the auto assignments will be distributed over several places inside the function - you definitely don't want this.

Here my rule-of-thumbs would be:

  • when you want a function template, and your compiler supports C++20, use C++20 syntax as long as it does not make the code less obvious and does not hide type dependencies.

  • Otherwise, use the older template syntax with explicit parameters.

  • And when you do not really need a generic solution, because you don't have a requirement for different types, do not use a template at all, neither the abbreviated syntax, nor the explicit one.

October 6, 2025 Score: 5 Rep: 12,369 Quality: Medium Completeness: 100%

There is an middle ground between being tied to an explicit type, and having no guidance about what is accepted. With the following, you are not tied to specific types, but you do specify up-front what you expect from a parameter.

static auto maxValue( const std::ranges::range auto& map, const std::totallyorderedwith auto min)

Although this is probably more suited for a non-abbreviated template, because we want to use the type of map in the constraint for min.

template static Value maxValue(const Map& map, const Value min)

Those two forms are semantically equivalent, but not syntactically interchangeable1. You can also mix and match where constraints appear.

template static Value maxValue( const Map& map, const std::totallyorderedwith auto min)

It's perfectly sensible, when iterating over a map-like container, to use a structured binding in a range-for loop to separate the keys and the values. Doing that requires using auto, you couldn't use const std::pair & instead of const auto& there.

However, in C++26, your function body can be made more declarative:

return std::ranges::max(std::views::concat(std::views::single(min), map | std::views::values));
  1. It is ill-formed to declare a template with constraints expressed in one syntax, and later redeclare it with constraints expressed in another.
October 5, 2025 Score: 2 Rep: 4,782 Quality: Low Completeness: 80%

API

Argument types are to be explicit in API and may be implicit in implementation. If the function in question is private, anything goes. If the function is public, no type should be left out.

The rationale is:

  • API should be as narrow as reasonable, it can only be extended when absolutely neccessary. API is easy to extend, but is very hard to shrink (shrinking breaks backward compatibility and requires a labor intensive deprecation protocol). Therefore adding any methods (and extending acceptable parameter range) to an API requires careful consideration and should be done as late as reasonable.
  • Non-local function calls may require updates when function implementation changes, but the compiler would not detect the incompatibility when auto is used. For example - function may start to make assumptions about collection behavior:
    • with explicit types, it changes the argument type, and all incompatible call sites are highlighted by compiler
    • with auto formally compatible call sites will break in runtime.

The funny part - the reverse is also true, if you use auto in a function signature, you HAVE to hide this function behind static, private, whatever scoping you have. The potential exception is a library of generic algorithms, where abstract inputs are expected and the exposed function is a primary responsibility of a library.

Range-based for loops

Personally I do not use value-type derivation in range loops, however, I did not work with those in C++ so my experience is not applicable.

December 16, 2025 Score: 1 Rep: 45,000 Quality: Medium Completeness: 60%

My immediate reaction would be that neither is really ideal.

In this case, a "normal" function template may make the most sense. Consider the original signature:

static int maxValue(const std::unorderedmap& map, const int min) {

It's perfectly reasonable that some of the types here could vary...but it probably is important that the second parameter matches (or at least can be converted to) the same type as the second item in a map element, and the return value should be the same type as well. So as a starting point we probably want something like this:

template 
static T maxValue(const std::unorderedmap &map, const T min)

We don't care about the type of the key either though. In fact, we never pay any attention to the keys at all. That gives us something like this:

template 
static T maxValue(const std::unorderedmap &map, const T min)

But we don't really care about the type of map involved either. It could be an (ordered) map, an unordered map, a flat map, or something based on splay lists, or any number of other things. All we really care about is that it's something we can iterate through, and receive elements that are something like std::pair, so we can plug then into our structured binding and get the Key (which we ignore) and the associated value. That leads to something like this:

template 
static ValT maxValue(const MapT &map, ValT min) {

I'd also be tempted to eliminate the second parameter:

template 
static ValT maxValue(const MapT &map) {
    auto [, m] = *std::begin(map);
    auto max = m;
    for (auto const &[_, val] : map) {
        if (val > max) {
            max = val;
        }
    }
    return max;
}

This will produce a different result if the value you passed for min was larger than any of the values in the map, but I doubt that was really intended.

Eliminating that does remove some of our original motivation for using a template, but (in my opinion) only some of it. Although we could figure out that the return type was going to be the same as the mapped type in the map, I think there's still some value in directly documenting that fact.

The next step would be to add some concepts to directly document the the requirements on the map and value type (e.g., that you can do a