Document number:   N3390=12-0080
Date: 2012-09-23
Project: Programming Language C++
Reply-to: Beman Dawes <bdawes at acm dot org>
Kevlin Henney <kevlin at curbralan dot com>

Any Library Proposal (Revision 1)

Introduction
Motivation and Design
Design paths not taken
Revision History
Examples
FAQ
Proposed Wording
    ValueType Requirements
    Header <any> synopsis
    Class bad_any_cast
    Class any
    Non-member functions

Introduction

This paper proposes a type-safe container for single values of value types that is suitable for a Technical Specification (TS), the C++ Standard itself (C++1Y), or a new International Standard (IS).

The proposal is based on the Boost Any Library (see www.boost.org/libs/any). The Boost version of the library has been in wide use for over a decade. The proposal is a pure addition to the standard library, requires no modifications of existing headers, and, modulo the usual namespace caveats, will have no effect on existing code.

Motivation and Design

There are times when a generic (in the sense of general as opposed to template-based programming) type is needed: variables that are truly variable, accommodating values of many other more specific types rather than C++'s normal strict and static types. We can distinguish three basic kinds of generic type:

  1. Converting types that can hold one of a number of possible value types, e.g. int and string, and freely convert between them, for instance interpreting 5 as "5" or vice-versa. Such types are common in scripting and other interpreted languages. boost::lexical_cast supports such conversion functionality.
     
  2. Discriminated types that contain values of different types but do not attempt conversion between them, i.e. 5 is held strictly as an int and is not implicitly convertible either to "5" or to 5.0. Their indifference to interpretation but awareness of type effectively makes them safe, generic containers of single values, with no scope for surprises from ambiguous conversions.
     
  3. Indiscriminate types that can refer to anything but are oblivious to the actual underlying type, entrusting all forms of access and interpretation to the programmer. This niche is dominated by void *, which offers plenty of scope for surprising, undefined behavior.

The proposed any class (based on the class of the same name described in "Valued Conversions" by Kevlin Henney, C++ Report 12(7), July/August 2000) is a variant value type based on the second category. It supports copying of any value type and safe checked extraction of that value strictly against its type.

Design paths not taken

A similar design, offering more appropriate operators, could be used for a generalized function adaptor, a generalized iterator adaptor, and other object types that need uniform runtime treatment but support only compile-time template parameter conformance. Such components are not proposed here.

Revision History

N3390 - Revision 1

Sean Parent of Adobe Systems kindly provided his C++11 version of the any interface to aid in preparing this proposal. Sean has done extensive experiments with real C++11 compilers on issues such as pass-by-value versus pass-by-reference. Changes annotated above are strongly influenced by his suggestions.

N1939 - Initial paper

Examples

The following code demonstrates the syntax for using implicit conversions to and copying of any objects:

#include <list>
#include <any>

using std::tbd::any_cast;
using std::tbd::any;

typedef std::list<any> many;

void append_int(many& values, int value)
{
    any to_append = value;
    values.push_back(to_append);
}

void append_string(many& values, const std::string& value)
{
    values.push_back(value);
}

void append_char_ptr(many& values, const char* value)
{
    values.push_back(value);
}

void append_any(many& values, const any& value)
{
    values.push_back(value);
}

void append_nothing(many& values)
{
    values.push_back(());
}

The following predicates follow on from the previous definitions and demonstrate the use of queries on any objects:

bool is_empty(const any& operand)
{
    return operand.empty();
}

bool is_int(const any& operand)
{
    return operand.type() == typeid(int);
}

bool is_char_ptr(const any& operand)
{
    try
    {
        any_cast<const char *>(operand);
        return true;
    }
    catch(const std::tr2::bad_any_cast&)
    {
        return false;
    }
}

bool is_string(const any& operand)
{
    return any_cast<std::string*>(&operand);
}

void count_all(many& values, std::ostream& out)
{
    out << "#empty == "
        << std::count_if(values.begin(), values.end(), is_empty) << std::endl;
    out << "#int == "
        << std::count_if(values.begin(), values.end(), is_int) << std::endl;
    out << "#const char * == "
        << std::count_if(values.begin(), values.end(), is_char_ptr) << std::endl;
    out << "#string == "
        << std::count_if(values.begin(), values.end(), is_string) << std::endl;
}

The following type, patterned after the OMG's Property Service, defines name-value pairs for arbitrary value types:

struct property
{
    property();
    property(const std::string&, const any&);

    std::string name;
    any value;
};

typedef std::list<property> properties;

The following base class demonstrates one approach to runtime polymorphism based callbacks that also require arbitrary argument types. The absence of virtual member templates requires that different solutions have different trade-offs in terms of efficiency, safety, and generality. Using a checked variant type offers one approach:

class consumer
{
public:
    virtual void notify(const any&) = 0;
    ...
};

FAQ

What is the relationship between Boost.any and Boost.variant?

Boost::any is like a "typesafe void*", while Boost::variant is a "typesafe union".

Proposed Wording

This clause describes components that C++ programs may use to perform operations on objects of a discriminated type.

[Note: The discriminated type may contain values of different types but does not attempt conversion between them, i.e. 5 is held strictly as an int and is not implicitly convertible either to "5" or to 5.0. This indifference to interpretation but awareness of type effectively allows safe, generic containers of single values, with no scope for surprises from ambiguous conversions. -- end note.]

ValueType Requirements

A ValueType type shall meet the requirements for CopyConstructible [copyconstructible].  The strong exception-safety guarantee is required for all forms of assignment.

[Note: Values are strongly informational objects for which identity is not significant, i.e. the focus is principally on their state content and any behavior organized around that. Another distinguishing feature of values is their granularity: normally fine-grained objects representing simple concepts in the system such as quantities.

As the emphasis of a value lies in its state not its identity, values can be copied and typically assigned one to another, requiring the explicit or implicit definition of a public copy constructor and public assignment operator. Values typically live within other scopes, i.e. within objects or blocks, rather than on the heap. Values are therefore normally passed around and manipulated directly as variables or through references, but not as pointers that emphasize identity and indirection. --end note]

Header <any> synopsis

Additions from the prior version are in green with underscores, deletions are in red with strikethroughs.

namespace std { namespace tbd {
  class bad_any_cast : public std::bad_cast
  {
  public:
    virtual const char* what() const;
  };

  class any
  {
  public:
    // construct/destruct
    any();

    any(const any& other);
    any(any&& x) noexcept;

    template<typename ValueType>
      any(const ValueType& value);

   ~any() noexcept;
  
    // assignments
    any& operator=(const any& rhs);
    any& operator=(any&& rhs) noexcept;

    template<typename ValueType>
      any& operator=(const ValueType& rhs);

    // modifiers
    any& swap(any& rhs) noexcept;

    // observers
    bool empty() const noexcept;
    const type_info& type() const noexcept;
  };

  void swap(any& x, any& y) noexcept;

  template<typename ValueType>
    ValueType any_cast(const any& operand);

  template<typename ValueType>
    ValueType any_cast(any& operand);

  template<typename ValueType>
    const ValueType* any_cast(const any* operand) noexcept;

  template<typename ValueType>
    ValueType* any_cast(any* operand) noexcept;

}}

Class bad_any_cast

Objects of type bad_any_cast are thrown by a failed any_cast.

Class any

Objects of class any hold instances of any type that satisfies the ValueType requirements.

any construct/destruct

any();

Postconditions: this->empty()

any(const any& other);

Effects: Copies content of other into a new instance, so that any content is equivalent in both type and value to other, or empty if other is empty.

Throws: bad_alloc or any exceptions arising from the copy constructor of the contained type.

any(any&& other) noexcept;

Effects: Moves content of other into a new instance, so that any content is equivalent in both type and value to the original content of other, or empty if other is empty.

template<typename ValueType>
  any(ValueType value);

Effects: Constructs an object of type any with initial content equivalent in both type and value to value.

Throws: bad_alloc or any exceptions arising from the copy constructor of the contained type.

~any() noexcept;

Effects: Releases resources.

any assignments

any& operator=(any rhs);

Effects: rhs.swap(*this)

Throws: bad_alloc or any exceptions arising from the copy constructor of the contained type. Assignment satisfies the strong guarantee of exception safety.

any& operator=(any&& rhs) noexcept;

Effects: Moves content of rhs to *this, so that any content is equivalent in both type and value to the original content of rhs, or empty if rhs is empty.

template<typename ValueType>
  any& operator=(ValueType rhs);

Effects: any(rhs).swap(*this)

Returns: *this

Throws: bad_alloc or any exceptions arising from the copy constructor of the contained type. Assignment satisfies the strong guarantee of exception safety.

any modifiers

any& swap(any& rhs) noexcept;

Effects: Exchange of the contents of *this and rhs.

Returns: *this

any observers

bool empty() const noexcept;

Returns: true if instance is empty, otherwise false.

const type_info& type() const noexcept;

Returns: The typeid of the contained value if instance is non-empty, otherwise typeid(void).

[Note: Useful for querying against types known either at compile time or only at runtime. --end note]

Non-member functions

void swap(swap(any& x, any& y) noexcept; 
Effects: x.swap(y).
template<typename ValueType>
  ValueType any_cast(const any& operand);
template<typename ValueType>
  ValueType any_cast(any& operand);

Returns: The value contained by operand.

Throws: bad_any_cast if unsuccessful.

[Note: For consistency with the C++ keyword casts, a copy is returned.--end note.]

template<typename ValueType>
  ValueType any_cast(const any* operand) noexcept;
template<typename ValueType>
  ValueType any_cast(any* operand) noexcept;

Returns: A pointer to the value contained by operand if successful, otherwise nullptr. The returned pointer is const qualified for the signature with a const argument.


© Copyright 2001 Kevlin Henney
© Copyright 2006, 2012 Beman Dawes

Revised 2012-09-23