DIP63

From D Wiki
Revision as of 18:27, 15 June 2014 by Dicebot (talk | contribs)
Jump to: navigation, search
Title: Operator overloading for raw templates
DIP: 63
Version: 1
Status: Draft
Created: 2014-06-15
Last Modified: 2013-06-15
Author: Михаил Сташун (Dicebot)
Links:

Abstract

This DIP proposes solution for user-defined types with semantics similar to those of built-in template argument lists. It can be achieved by overloading operators inside template symbols that don't resolve to any valid type.

Rationale

During discussion and implementation of DIP54 it was figured out that we are currently lacking any hygienic way to defined packed argument lists. Existing solutions degrade into unpacked lists on any operation and are thus not robust enough to fit into standard library. This can be achieved by allowing overloads for static opSlice and similar operators. To avoid ambiguity with definition of array types those should be applicable to something that is not a valid type on its own - template symbol.

Description

On the origin of proposal

Most simple implementation of packed argument list looks like this:

template Pack(T...)
{
    alias expand = T;
}

It is commonly used as a quick solution but lacks any convenience. Packing is achieved only for passing as template argument, any actual inter-operation requires expansion. This is both error-prone and pollutes code with lot of otherwise unneeded .expand suffixes. Somewhat more robust implementation uses aggregate and alias this:

struct Pack(T...)
{
    alias expand = T;
    alias expand this;
}

This makes it possible to use template argument list operations on a pack without explicit .expand which is a big advantage. However, any such operation (like slicing) will return expanded template argument list which needs to be explicitly packed again to preserve hygiene. In practice such solution is also hardly acceptable for Phobos.

Obvious fix is to allow template operator overloads:

struct Pack(T...)
{
    alias expand = T;

    alias opSlice(size_t lower, size_t upper) = Pack!(T[lower..upper]);
}

However this does not work for all desired operators because of ambiguity with array declaration syntax:

struct Pack(T...)
{
    alias expand = T;

    alias opIndex(size_t index) = T[index];
}

// Pack!(int, int)[1] -> integer or array of structs?

To address that we can benefit from the fact that non-eponymous template symbols are not a valid types in D:

template SomeTemplate(T...)
{
}

static assert (!is(SomeTemplate!int));

So final solution may look something like this:

template Pack(T...)
{
    alias expand = T;

    alias opIndex(size_t index) = T[index];
    alias opSlice(size_t lower, size_t upper) = Pack!(T[lower..upper]);
    alias opDollar = T.length;
}

alias element = Pack!(int, int)[1]; // no ambiguity as Pack!(int, int) is not a valid type

Instead of defining opApply for such type compiler can detect if opIndex and opDollar are defined and generate static foreach code same as it is done for built-in template argument lists.

Summary of proposed semantics

  1. allow defining opIndex, opSlice and opDollar as aliases / enums inside templates
  2. check for presence of those when matching operation is attempted on template symbol that does not resolve to a type
  3. if such non-type template is used inside foreach loop, rewrite it as iteration from opIndex[0] to opIndex[$]
  4. template symbol itself is still not considered proper type by is expression

Backwards compatibility

Almost perfect, no semantics of currently working code are changed. Only way to be influenced by the change is to query for specific semantics of non-type template symbols via is expression, which is extremely uncommon thing to do.

Copyright

This document has been placed in the Public Domain.