ISO/ IEC JTC1/SC22/WG21 N3854

Document number: N3854
Date: 2014-01-17
Project: Programming Language C++, Library Evolution Working Group
Reply-to: Stephan T. Lavavej <stl@microsoft.com>


Variable Templates For Type Traits


I. Introduction

This is a proposal to add variable templates like is_same_v<T, U>
as less-verbose synonyms for type traits like is_same<T, U>::value.


II. Motivation And Scope

Due to the C++98/03 Core Language's limitations, Boost/TR1's type traits
wrapped typedefs and constants in struct templates. After the C++11 Core
Language gained alias templates, Walter E. Brown transformed the horrific
"typename decay<T>::type" into the elegant decay_t<T> (see [1]). Now that
Gabriel Dos Reis has added variable templates to the C++14 Core Language
(see [2]), the C++17 Standard Library should be updated accordingly.

Variable templates like is_same_v<T, U> are superior to nested constants like
is_same<T, U>::value for several reasons:

1. Obviously, the former is 5 characters shorter.

2. Less obviously, the former eliminates "disconnected verbosity". When working
with type traits, it's common to have to say things like
enable_if_t<!is_same<X, decay_t<Y>>::value, Z>, where "is_same" and "::value"
are separated by a potentially substantial amount of machinery.

3. Having to say "::value" isn't a feature, it's a workaround for a language
limitation that has been solved. (Note that this proposal doesn't touch the
struct templates, so metaprogrammers who want the is_same type instead of its
nested constant are unaffected.)

If type traits were being designed from scratch today, I believe that they
would not be provided as struct templates, but purely as alias templates and
variable templates. (Indeed, ratio_add/etc. are purely alias templates, see
20.11.4 [ratio.arithmetic].) We can't go back in time and change the design of
type traits, but we can provide their ideal forms at the cost of 2 characters.

Note that there are several ways to request constants from type traits:
(In these examples, "()" can be used instead of "{}".)

is_same<T, U>::value   // TR1

                       // C++11, see [3]
is_same<T, U>{}        // context must perform implicit conversion
(bool) is_same<T, U>{} // any context, C-style cast
bool(is_same<T, U>{})  // any context, functional-notation C-style cast
static_cast<bool>(is_same<T, U>{}) // any context

is_same<T, U>{}()      // C++14, see [4]

is_same_v<T, U>        // this proposal

Do we really need another form of syntax for this? I argue that the answer
is yes. While constructing an object and implicitly converting it to bool,
explicitly converting it to bool, or invoking its function call operator can be
useful in certain situations, it is somewhat unintuitive. In contrast, it's
simple to learn that "_v" means "get the value" like how "_t" means
"get the type". Additionally, observe that constructing a temporary object also
produces "disconnected verbosity", where "{}" (or "()") must appear after the
closing ">". Even for expert programmers, it's easier to close deeply nested
templates with ">>>>", instead of having to insert extra braces or parentheses.

This proposal is not based on existing practice, but it's
self-implementing - see "V. Standardese" and "VIII. Implementation" below.


III. Impact On The Standard

This proposal has no dependencies beyond Working Paper N3797, except for
assuming that the Proposed Resolutions for LWG 2313 (see [5]) and LWG 2317
(see [6]) will be adopted. These PRs fix tuple_size, alignment_of, rank,
and extent's underspecified types, requiring them to be size_t.

I am not aware of any interactions between this proposal and other proposals,
except that if any constant type traits are added (e.g. is_callable),
corresponding variable templates should also be added.

This proposal is a pure extension. It doesn't affect existing user code at all,
except for adding new names to namespace std.


IV. Design Decisions

This proposal uses an "_v" suffix to replace "::value", following N3655's
use of "_t" to replace "::type" (see [1]).

This proposal adds variable templates for all of the UnaryTypeTraits and
BinaryTypeTraits in <type_traits>, and for the rest of the Standard Library's
"::value" machinery. (I believe this is an exhaustive list.) Some of these
(e.g. tuple_size_v) will be as frequently used as <type_traits>. While others
(e.g. is_bind_expression_v) will be extremely obscure, I believe that adding
variable templates for everything is valuable - this allows users to learn
the simple rule "just say _t and _v" without exceptions.

While we're in the neighborhood, this proposal adds a single alias template,
tuple_element_t. A reference to this is accidentally present in Working Paper
N3797, as noted by editorial issues 221 and 247 (see [7] and [8]). This alias
template should exist for consistency with the type traits alias templates,
for consistency with this proposal's tuple_size_v, and (most importantly)
because "typename ::type" is so horrible.

There are probably more candidates for variable templates throughout the
Standard Library, e.g. duration_values<Rep>::zero()/min()/max()
(20.12.4.2 [time.traits.duration_values]) and numeric_limits (18.3.2 [limits]).
This proposal is limited to replacing "::value" systematically.


V. Standardese

1. In 20.10.2 [meta.type.synop], at the end of the
"// 20.10.4.1, primary type categories:" chunk, add:

template <class T> constexpr bool is_void_v
  = is_void<T>::value;
template <class T> constexpr bool is_null_pointer_v
  = is_null_pointer<T>::value;
template <class T> constexpr bool is_integral_v
  = is_integral<T>::value;
template <class T> constexpr bool is_floating_point_v
  = is_floating_point<T>::value;
template <class T> constexpr bool is_array_v
  = is_array<T>::value;
template <class T> constexpr bool is_pointer_v
  = is_pointer<T>::value;
template <class T> constexpr bool is_lvalue_reference_v
  = is_lvalue_reference<T>::value;
template <class T> constexpr bool is_rvalue_reference_v
  = is_rvalue_reference<T>::value;
template <class T> constexpr bool is_member_object_pointer_v
  = is_member_object_pointer<T>::value;
template <class T> constexpr bool is_member_function_pointer_v
  = is_member_function_pointer<T>::value;
template <class T> constexpr bool is_enum_v
  = is_enum<T>::value;
template <class T> constexpr bool is_union_v
  = is_union<T>::value;
template <class T> constexpr bool is_class_v
  = is_class<T>::value;
template <class T> constexpr bool is_function_v
  = is_function<T>::value;

2. In 20.10.2 [meta.type.synop], at the end of the
"// 20.10.4.2, composite type categories:" chunk, add:

template <class T> constexpr bool is_reference_v
  = is_reference<T>::value;
template <class T> constexpr bool is_arithmetic_v
  = is_arithmetic<T>::value;
template <class T> constexpr bool is_fundamental_v
  = is_fundamental<T>::value;
template <class T> constexpr bool is_object_v
  = is_object<T>::value;
template <class T> constexpr bool is_scalar_v
  = is_scalar<T>::value;
template <class T> constexpr bool is_compound_v
  = is_compound<T>::value;
template <class T> constexpr bool is_member_pointer_v
  = is_member_pointer<T>::value;

3. In 20.10.2 [meta.type.synop], at the end of the
"// 20.10.4.3, type properties:" chunk, add:

template <class T> constexpr bool is_const_v
  = is_const<T>::value;
template <class T> constexpr bool is_volatile_v
  = is_volatile<T>::value;
template <class T> constexpr bool is_trivial_v
  = is_trivial<T>::value;
template <class T> constexpr bool is_trivially_copyable_v
  = is_trivially_copyable<T>::value;
template <class T> constexpr bool is_standard_layout_v
  = is_standard_layout<T>::value;
template <class T> constexpr bool is_pod_v
  = is_pod<T>::value;
template <class T> constexpr bool is_literal_type_v
  = is_literal_type<T>::value;
template <class T> constexpr bool is_empty_v
  = is_empty<T>::value;
template <class T> constexpr bool is_polymorphic_v
  = is_polymorphic<T>::value;
template <class T> constexpr bool is_abstract_v
  = is_abstract<T>::value;
template <class T> constexpr bool is_signed_v
  = is_signed<T>::value;
template <class T> constexpr bool is_unsigned_v
  = is_unsigned<T>::value;
template <class T, class... Args> constexpr bool is_constructible_v
  = is_constructible<T, Args...>::value;
template <class T> constexpr bool is_default_constructible_v
  = is_default_constructible<T>::value;
template <class T> constexpr bool is_copy_constructible_v
  = is_copy_constructible<T>::value;
template <class T> constexpr bool is_move_constructible_v
  = is_move_constructible<T>::value;
template <class T, class U> constexpr bool is_assignable_v
  = is_assignable<T, U>::value;
template <class T> constexpr bool is_copy_assignable_v
  = is_copy_assignable<T>::value;
template <class T> constexpr bool is_move_assignable_v
  = is_move_assignable<T>::value;
template <class T> constexpr bool is_destructible_v
  = is_destructible<T>::value;
template <class T, class... Args> constexpr bool is_trivially_constructible_v
  = is_trivially_constructible<T, Args...>::value;
template <class T> constexpr bool is_trivially_default_constructible_v
  = is_trivially_default_constructible<T>::value;
template <class T> constexpr bool is_trivially_copy_constructible_v
  = is_trivially_copy_constructible<T>::value;
template <class T> constexpr bool is_trivially_move_constructible_v
  = is_trivially_move_constructible<T>::value;
template <class T, class U> constexpr bool is_trivially_assignable_v
  = is_trivially_assignable<T, U>::value;
template <class T> constexpr bool is_trivially_copy_assignable_v
  = is_trivially_copy_assignable<T>::value;
template <class T> constexpr bool is_trivially_move_assignable_v
  = is_trivially_move_assignable<T>::value;
template <class T> constexpr bool is_trivially_destructible_v
  = is_trivially_destructible<T>::value;
template <class T, class... Args> constexpr bool is_nothrow_constructible_v
  = is_nothrow_constructible<T, Args...>::value;
template <class T> constexpr bool is_nothrow_default_constructible_v
  = is_nothrow_default_constructible<T>::value;
template <class T> constexpr bool is_nothrow_copy_constructible_v
  = is_nothrow_copy_constructible<T>::value;
template <class T> constexpr bool is_nothrow_move_constructible_v
  = is_nothrow_move_constructible<T>::value;
template <class T, class U> constexpr bool is_nothrow_assignable_v
  = is_nothrow_assignable<T, U>::value;
template <class T> constexpr bool is_nothrow_copy_assignable_v
  = is_nothrow_copy_assignable<T>::value;
template <class T> constexpr bool is_nothrow_move_assignable_v
  = is_nothrow_move_assignable<T>::value;
template <class T> constexpr bool is_nothrow_destructible_v
  = is_nothrow_destructible<T>::value;
template <class T> constexpr bool has_virtual_destructor_v
  = has_virtual_destructor<T>::value;

4. In 20.10.2 [meta.type.synop], at the end of the
"// 20.10.5, type property queries:" chunk, add:

template <class T> constexpr size_t alignment_of_v
  = alignment_of<T>::value;
template <class T> constexpr size_t rank_v
  = rank<T>::value;
template <class T, unsigned I = 0> constexpr size_t extent_v
  = extent<T, I>::value;

5. In 20.10.2 [meta.type.synop], at the end of the
"// 20.10.6, type relations:" chunk, add:

template <class T, class U> constexpr bool is_same_v
  = is_same<T, U>::value;
template <class Base, class Derived> constexpr bool is_base_of_v
  = is_base_of<Base, Derived>::value;
template <class From, class To> constexpr bool is_convertible_v
  = is_convertible<From, To>::value;

6. In 20.4.1 [tuple.general]/2, at the end of the
"// 20.4.2.5, tuple helper classes:" chunk, add:

template <class T> constexpr size_t tuple_size_v
  = tuple_size<T>::value;
template <size_t I, class T> using tuple_element_t
  = typename tuple_element<I, T>::type;

7. In 20.11.2 [ratio.syn], at the end of the
"// 20.11.5, ratio comparison" chunk, add:

template <class R1, class R2> constexpr bool ratio_equal_v
  = ratio_equal<R1, R2>::value;
template <class R1, class R2> constexpr bool ratio_not_equal_v
  = ratio_not_equal<R1, R2>::value;
template <class R1, class R2> constexpr bool ratio_less_v
  = ratio_less<R1, R2>::value;
template <class R1, class R2> constexpr bool ratio_less_equal_v
  = ratio_less_equal<R1, R2>::value;
template <class R1, class R2> constexpr bool ratio_greater_v
  = ratio_greater<R1, R2>::value;
template <class R1, class R2> constexpr bool ratio_greater_equal_v
  = ratio_greater_equal<R1, R2>::value;

8. In 19.5 [syserr]/2, after the definition of is_error_condition_enum, add:

template <class T> constexpr bool is_error_code_enum_v
  = is_error_code_enum<T>::value;
template <class T> constexpr bool is_error_condition_enum_v
  = is_error_condition_enum<T>::value;

9. In 20.7.2 [memory.syn]/1, at the end of the
"// 20.7.7, uses_allocator" chunk, add:

template <class T, class Alloc> constexpr bool uses_allocator_v
  = uses_allocator<T, Alloc>::value;

10. In 20.9 [function.objects]/2, after the declaration of is_placeholder, add:

template <class T> constexpr bool is_bind_expression_v
  = is_bind_expression<T>::value;
template <class T> constexpr int is_placeholder_v
  = is_placeholder<T>::value;

11. In 20.12.2 [time.syn], at the end of the
"// 20.12.4, customization traits" chunk, add:

template <class Rep> constexpr bool treat_as_floating_point_v
  = treat_as_floating_point<Rep>::value;


VI. Acknowledgements

Thanks to Angel Hernandez Matos, Bill Plauger, Colin Jeanne, Jon Kalb,
Jonathan Caves, Robert Schumacher, Scott Meyers, and Sridhar Madhugiri
for reviewing this proposal.

Thanks to Stephen MacKenzie for testing my implementation with clang.

Thanks to Walter E. Brown for reminding me about LWG 1019 and N3545.


VII. References

All of the Standardese citations in this proposal are to Working Paper N3797:
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3797.pdf

[1] N3655 "TransformationTraits Redux, v2" by Walter E. Brown:
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3655.pdf

[2] N3651 "Variable Templates (Revision 1)" by Gabriel Dos Reis:
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3651.pdf

[3] LWG 1019 "Make integral_constant objects useable in
integral-constant-expressions" submitted by Alisdair Meredith:
http://cplusplus.github.io/LWG/lwg-defects.html#1019

[4] N3545 "An Incremental Improvement to integral_constant" by Walter E. Brown:
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3545.pdf

[5] LWG 2313 "tuple_size should always derive from
integral_constant<size_t, N>" submitted by Stephan T. Lavavej:
http://cplusplus.github.io/LWG/lwg-active.html#2313

[6] LWG 2317 "The type property queries should be UnaryTypeTraits
returning size_t" submitted by Stephan T. Lavavej:
http://cplusplus.github.io/LWG/lwg-active.html#2317

[7] Editorial Issue 221 "Re use of new type traits aliases"
opened by W-E-Brown:
https://github.com/cplusplus/draft/issues/221

[8] Editorial Issue 247 "std::tuple_element_t<> used without being defined"
opened by mpark:
https://github.com/cplusplus/draft/issues/247


VIII. Implementation

This successfully compiled with clang 3.5 (trunk 199135):

#include <chrono>
#include <cstddef>
#include <functional>
#include <memory>
#include <ratio>
#include <system_error>
#include <tuple>
#include <type_traits>

namespace std {

    [ALL OF THIS PROPOSAL'S STANDARDESE EXCEPT treat_as_floating_point_v]

    namespace chrono {
        template <class Rep> constexpr bool treat_as_floating_point_v
          = treat_as_floating_point<Rep>::value;
    } // namespace chrono
} // namespace std

static_assert(std::is_same_v<short, std::tuple_element_t<2,
    std::tuple<long, int, short, char>>>, "BOOM");

(end)