0

I have been able to find reference material at cppreference.com, cplusplus.com, and this site (What is a scalar Object in C++?) that enables me to determine whether a particular C++ data type is a scalar. Namely, I can apply a mental algorithm that runs like this: "Is it a reference type, a function type, or void? If not, is it an array, class, or union? If not, it's a scalar type." In code, of course, I can apply std::is_scalar<T>. And finally, I can apply the working definition "A scalar type is a type that has built-in functionality for the addition operator without overloads (arithmetic, pointer, member pointer, enum and std::nullptr_t)."

What I have not been able to find is a description of the purpose of the scalar classification. Why would anyone care if something is a scalar? It seems like a kind of "leftover" classification, like "reptile" in zoological taxonomy ("Well, a reptile is, um, an amniote that's not a bird or a mammal"). I'm guessing that it must have some use to justify its messiness. I can understand why someone would want to know whether a type is a reference -- you can't take a reference of a reference, for instance. But why would people care whether something is a scalar? What is scalarness all about?

Community
  • 1
  • 1
Alan
  • 1,889
  • 2
  • 18
  • 30
  • 1
    @JoachimPileborg: I think Alan gets what scalar means, the question is: what (presumably templated) code would I write where I would ever actually care if the type is a scalar? I'm guessing `is_scalar` exists for the sake of completeness, not out of any motivating use-case, but maybe someone else can say for sure. – GManNickG Sep 02 '16 at 22:18
  • 1
    Perhaps to write more efficient template code: it might be faster to pass around pointers to a more complex type than to make copies of them, or you might need locks to implement atomic operations on them, but on most architectures, you can store a properly-aligned scalar in a register and read or update it as an atomic hardware instruction. – Davislor Sep 02 '16 at 22:29
  • The thing with type-traits is that they are not used in isolation, or when the type of something is known. They are mostly used in template meta-programming and SFINAE, to e.g. select a specific function based on the type of a template argument. And if you need some specialization for a type that is "a type that has built-in functionality for the addition operator without overloads (arithmetic, pointer, member pointer, enum and std::nullptr_t)" then `std::is_scalar` is the correct function to use. – Some programmer dude Sep 02 '16 at 22:29
  • @Lorehead, if that comment were an answer, I would vote it up. – Alan Sep 03 '16 at 15:47
  • @Alan All right, there you go. – Davislor Sep 03 '16 at 22:36
  • @Alan: "*What I have not been able to find is a description of the purpose of the scalar classification.*" And I don't understand the purpose of your *question*. We have type-traits that categorize types. "Scalar type" is a category of types defined by the standard library. Why does `is_scalar` need more justification than that? – Nicol Bolas Sep 03 '16 at 22:46

3 Answers3

3

Given is_scalar<T>, you can be sure that operator=(), operator==() and operator!=() does what you think (that is, assignment, comparison and the inverse of that, respectively) for any T.

  • a class T might or might not have any of these, with arbitrary meaning;
  • a union T is problematic;
  • a function doesn't have =;
  • a reference might hold any of these;
  • an array - well for two arrays of different size, == and != will make it decay to pointer and compare, while = will fail compile-time.

Thus if you have is_scalar<T>, you can be sure that these work consistently. Otherwise, you need to look further.

lorro
  • 10,687
  • 23
  • 36
  • You can also be sure, that all other operators do what you expect them to do -- `+ - * / % & | ^ ~`, right? – Zereges Sep 03 '16 at 06:20
  • 1
    @lorro: "*Otherwise, you need to look further.*" You could just look further to begin with and not bother with `is_scalar`. After all, if you use SFINAE to determine if a type can be `==` compared, it will be just as true for scalar types as for other types. – Nicol Bolas Sep 03 '16 at 22:52
  • @Zereges: some of them are defined, some of them are not; e.g., none of the above list has a common meaning across arithmetic types and pointers. You could define `!` as `!=0` for these types, but that's basically all. – lorro Sep 05 '16 at 20:24
  • @NicolBolas: `==` might not do what you think for non-scalar types even if it's valid from a design perspective. An object might store a log of its lifetime and the current value and compare `==` for current value. A simple cache that uses `==` and unifies across threads will then unify objects with different history. You don't throw out the cache; you just specialize it with a comparison (or perhaps unification). For `is_scalar<>`, this is probably unnecessary (e.g, you might safely define a convenience type for that). Even worse for `=`, which might _partially_ tie lhs to rhs for classes. – lorro Sep 05 '16 at 20:33
  • @lorro: "*== might not do what you think for non-scalar types even if it's valid from a design perspective.*" Yes, but that's either the fault of the user for providing an inappropriate `operator==` overload, or the fault of your design for using `operator==` as the basis of your function. But there is almost never a good reason to restrict an interface to scalar types *only*. You don't see the unordered containers disregarding operator== just because it might not do what you want; they simply provide a means for your to give them an *alternative*. – Nicol Bolas Sep 05 '16 at 20:36
  • @NicolBolas: I'm not saying to restrict; I'm saying to specialize. To optimize. To assume O(1) comparison. To have bold assumptions on memory usage. To _know_ that, after `decltype(b) a=b`, calling (almost..) anything on `b` won't change `b` to a third value (which is the case for partial sharing of state, e.g., resources). And yes, it's the best to provide a way to specify an alternative, we agree on that :) – lorro Sep 05 '16 at 21:02
  • @lorro: "*To know that, after decltype(b) a=b, calling (almost..) anything on b won't change b to a third value (which is the case for partial sharing of state, e.g., resources).*" If it's important to your template to be able to do that, then that is part of the template's interface. `sort` makes such "assumptions", though obviously not based on `operator==`. Those are not "assumptions"; those are requirements of your template. – Nicol Bolas Sep 05 '16 at 22:08
1

One purpose is to write more efficient template specializations. On many architectures, it would be more efficient to pass around pointers to objects than to copy them, but scalars can fit into registers and be copied with a single machine instruction. Or a generic type might need locking, while the machine guarantees that it will read or update a properly-aligned scalar with a single atomic instruction.

Davislor
  • 14,674
  • 2
  • 34
  • 49
  • I think this is the most likely answer. Abrahams and Gurtovoy's "C++ Template Metaprogramming" (p. 63) supports your answer: "... a generic function ought to accept parameters by reference. That said, it's usually wasteful to pass anything so trivial as a scalar type by reference: on some compilers, scalars are passed by value in registers, but when passed by reference they are forced onto the stack. What's called for is a metafunction, param_type, that returns T when it is a scalar, and T const & otherwise." – Alan Sep 03 '16 at 23:01
  • @Alan: And yet, every time you call a variadic function like `emplace`, you pass scalars by reference. – Nicol Bolas Sep 03 '16 at 23:06
  • Wouldn't this decision be made on the size of `T`, not if it's scalar? – GManNickG Sep 05 '16 at 16:30
  • @GManNickG: Not necessarily the size of `T`. Whether a type can be passed by register is a matter of several factors. For example, the Itanium ABI forbids passing a type by register if the type is not trivially copyable. The point being that while size is a necessary component of such things, size alone is insufficient to guarantee anything. – Nicol Bolas Sep 05 '16 at 20:38
0

Clue here in the notes on cppreference.com?

Each individual memory location in the C++ memory model, including the hidden memory locations used by language features (e.g virtual table pointer), has scalar type (or is a sequence of adjacent bit-fields of non-zero length). Sequencing of side-effects in expression evaluation, interthread synchronization, and dependency ordering are all defined in terms of individual scalar objects.

Richard Hodges
  • 68,278
  • 7
  • 90
  • 142