cpp-ru / ideas

Идеи по улучшению языка C++ для обсуждения
https://cpp-ru.github.io/proposals
Creative Commons Zero v1.0 Universal
89 stars 0 forks source link

Ключевое слово implementation(definition) #309

Open apolukhin opened 3 years ago

apolukhin commented 3 years ago

Перенос предложения: голоса +9, -1 Автор идеи: Mikhail Shostak

Добавить ключевое слово implementation (или definition) позволяющее определять блок в котором производится реализация структуры или класса для сокращения избыточности кода

Сейчас мы имеем следующий синтаксис для раздельного объявления и определения функций-членов и статических переменных. Выглядит это так:

//object.h
namespace scope
{

struct object
{
    void xxx();
    void yyy();
    void zzz();

    struct nested_object
    {
        using internal_type = size_t;
        static internal_type static_variable;
        internal_type member_variable;
        internal_type xxx();
        internal_type yyy();
        internal_type zzz();
    }
}

}

//object.cpp
namespace scope
{

void object::xxx() { /*code*/ }
void object::yyy() { /*code*/ }
void object::zzz() { /*code*/ }

object::nested_object::internal_type object::nested_object::static_variable = 0;
object::nested_object::internal_type object::nested_object::xxx() { /*code*/ }
object::nested_object::internal_type object::nested_object::yyy() { /*code*/ }
object::nested_object::internal_type object::nested_object::zzz() { /*code*/ }

}

В .cpp файле получается довольно много повторяющегося кода. С шаблонами лишнего кода становится еще больше. Возможно что-то подобное предлагали, но было бы неплохо иметь механизм в языке, который позволял бы не писать каждый раз 'object::nested_object::'. Использовать его можно примерно так (далее .cpp файл к предыдущему .h файлу):

//object.cpp
namespace scope
{

implementation object
{
    void xxx() { /*code*/ } //scope::object::xxx
    void yyy() { /*code*/ }
    void zzz() { /*code*/ }

    implementation nested_object
    {
        internal_type static_variable = 0; //scope::object::nested_object::internal_type scope::object::nested_object::static_variable
        internal_type xxx() { /*code*/ }
        internal_type yyy() { /*code*/ }

        internal_type member_variable = 0; //error: 'scope::object::nested_object::member_variable': is not a static member
        internal_type undeclared_variable = 0; //error: 'undeclared_variable': is not a member of 'scope::object::nested_object'
        inline void undeclared_function() { /*code*/ }  //error: 'undeclared_function': is not a member of 'scope::object::nested_object'
        //etc.
    };

    nested_object::internal_type nested_object::zzz() {} //old-style definition can be used also. should be resolved as scope::object::nested_object::internal_type scope::object::nested_object::zzz();
};

}

Идея довольно простая. К сожалению такое нельзя делать используя ключевое слово namespace потому, что компилятор не будет знать что программист хочет определить - глобальную функцию в пространстве имен или же функцию класса. Да и сам класс с пространством имен может конфликтовать. Но это можно сделать с помощью дополнительного ключевого слова. Очевидно, что минус введения нового ключевого слова это то, что старый код, который использует это слово может поломаться. (На самом деле это моё самое большое опосение по этому предложению. Но в c++11 же добавляли новые ключевые слова, возможно со скрипом, но всё же)

Что касается самой конструкции implementation object {}, то она просто говорит компилятору, что всё что определено внутри скобок будет относится к структуре object, в данном случае, которая находится в пространстве имен scope. Таким образом группировка кода позволяет уменьшить избыточность и повысить читабельность кода (Зависит от ситуации конечно. Я давно привык к синтаксису c++, ниже будет пример с шаблонами :P). Т.к. код сгруппирован, то следоватьно внутри блока будет только реализация конкретного класса. Это может поспособствовать организации кода. Возможно кому-то это покажется неудобным, т.к. не получится временно создать глобальную функцию/переменную внутри implementation блока и её придётся помещать в начало. С другой стороны предполагается, что у класса может быть несколько implementation блоков, если требуется вынести часть реализации в другое место или нужно сделать include чего-то прямо в середину файла (мало ли). Сам implementation по-сути работает как некое пространство, внутри которого можно не писать длинные конструкции с путями к реализуемой сущности и/или добавлять параметры шаблона относящиеся к классу для которого мы делаем имплементацию. Не уверен как компиляторы парсят определения функций, но на первый взгляд кажется, что implementaion блок может немного уменьшить время компиляции. Компилятору не нужно будет постоянно парсить типы для нового встреченного определения, он может взять тип из контекста, который у него есть после встречи ключевого слова implementation. Так же отпадает надобность добавлять названия классов в возвращаемые типы, если мы используем тип из нашего класса, ну в общем всё что можно из контекста блоков взять.

Теперь посмотрим на вариант с шаблонами. Есть, например, библиотека gml с математикой для OpenGL на манер GLSL. Там довольно много шаблонов с вынесенной реализацией в .inl файлы. Так же зависимость двух классов друг от друга (возможно это плохая архитектура) может потребовать вынести реализацию шаблонов из класса, чтобы можно было сделать #include другого файла (ну или если банально два небольших шаблонных класса с несколькими функциями находятся в одном файле, и реализацию снизу нужно написать). В общем шаблонный код может выглядеть как-то так:

//template_object.h
namespace scope
{

template<class T1>
struct template_object
{
    template<class T2>
    struct nested_object
    {
        using internal_type = size_t;
        static internal_type static_variable;

        template<class U1, class U2>
        T1 foo(U1 u1, U2 u2);

        template<class U1, class U2>
        T2 bar(U1 u1, U2 u2);
    };

    template<class U1, class U2>
    T1 xxx(U1 u1, U2 u2);

    template<class U1, class U2>
    T1 yyy(U1 u1, U2 u2);

    template<class U1, class U2>
    T1 zzz(U1 u1, U2 u2);
};

}

#include "template_object.inl"

//template_object.inl
namespace scope
{

template<class T1>
template<class T2>
template_object<T1>::nested_object<T2>::internal_type template_object<T1>::nested_object<T2>::static_variable = 0;

template<class T1>
template<class T2>
template<class U1, class U2>
inline T1 template_object<T1>::nested_object<T2>::foo(U1 u1, U2 u2) { /*code*/ }

template<class T1>
template<class T2>
template<class U1, class U2>
inline T2 template_object<T1>::nested_object<T2>::bar(U1 u1, U2 u2) { /*code*/ }

template<class T1>
template<class U1, class U2>
inline T1 template_object<T1>::xxx(U1 u1, U2 u2) { /*code*/ }

template<class T1>
template<class U1, class U2>
inline T1 template_object<T1>::yyy(U1 u1, U2 u2) { /*code*/ }

template<class T1>
template<class U1, class U2>
inline T1 template_object<T1>::zzz(U1 u1, U2 u2) { /*code*/ }

}

ту же самую реализацию можно было бы записать следующим образом:

//template_object.inl
namespace scope
{

template<class T1>
implementation template_object<T1>
{
    template<class T2>
    implementation nested_object<T2>
    {
        internal_type static_variable = 0;

        template<class U1, class U2>
        inline T1 foo(U1 u1, U2 u2) { /*code*/ }

        template<class U1, class U2>
        inline T2 bar(U1 u1, U2 u2) { /*code*/ }
    }

    template<class U1, class U2>
    inline T1 xxx(U1 u1, U2 u2) { /*code*/ }

    template<class U1, class U2>
    inline T1 yyy(U1 u1, U2 u2) { /*code*/ }

    template<class U1, class U2>
    inline T1 zzz(U1 u1, U2 u2) { /*code*/ }
}

}

Возможно такой вариант без шаблонных параметров U1 и U2 будет наглядней:

//template_object.inl
namespace scope
{

template<class T1>
template<class T2>
template_object<T1>::nested_object<T2>::internal_type template_object<T1>::nested_object<T2>::static_variable = 0;

template<class T1>
template<class T2>
inline T1 template_object<T1>::nested_object<T2>::foo() { /*code*/ }

template<class T1>
template<class T2>
inline T2 template_object<T1>::nested_object<T2>::bar() { /*code*/ }

template<class T1>
inline T1 template_object<T1>::xxx() { /*code*/ }

template<class T1>
inline T1 template_object<T1>::yyy() { /*code*/ }

template<class T1>
inline T1 template_object<T1>::zzz() { /*code*/ }

}
//vs
namespace scope
{

template<class T1>
implementation template_object<T1>
{
    template<class T2>
    implementation nested_object<T2>
    {
        internal_type static_variable = 0;
        inline T1 foo() { /*code*/ }
        inline T2 bar() { /*code*/ }
    }
    inline T1 xxx() { /*code*/ }
    inline T1 yyy() { /*code*/ }
    inline T1 zzz() { /*code*/ }
}

}

Ну и небольшой пример с c++ в перемешку с objective-c в котором есть подобная конструкция. Язык obj-c позволяет с её помощью делать много других интересных вищей, но это другая история. Так же хотелось бы упоминуть что в obj-c используется ключевое слово @implementation для этих целей. Поэтому поддержка подобного ключего слова для c++ не должна ломать .mm файлы. Возможно в таком случае лучше использовать ключевое слово definition в c++.

//FooBar.h
class Foo
{
    void foo();
};

@interface Bar : NSObject
-(void)bar;
@end

//FooBar.mm
implementation Foo
{
    void foo() { /*code*/ }
};

@implementation Bar
    -(void)bar { /*code*/ }
@end

На первый взгляд obj-c может показаться страшным, и я знаю c++ программистов, которые сразу вскрикивают услышав слово objective-c и начинают говорить про его ужасный синтаксис. Но проработав 2 года на obj-c и около 5 лет с c++. Моё мнение склоняется к тому, что obj-c выглядит более читабельным чем c++. Хотя, отчасти на это оказывают влияние спецефичные для obj-c механики, которые вряд ли появятся в c++. Моё мнение может быть субъективным. Так что хотелось бы получить объективную и конструктивную критику этого предложения. :)

apolukhin commented 3 years ago

yndx-antoshkka, 8 июня 2018, 15:48 Первый пример уже сейчас можно сильно сократить: https://godbolt.org/g/pEm57Y

Второй пример через алиасы не записать https://godbolt.org/g/BixZb4

Донести ваше предложение до стандарта будет безумно трудно, не уверен что игра стоит свечь... Надо прорабоать ситуации, когда struct и implementation различаются по полям/сигнатурам, можно ли объявлять вспомогательные вещи в implementation и какая у них будет область видимости, может ли быть несколько секций implementation в одной еднице трансляции и многое другое.

Вы готовы к трудностям?

Mikhail Shostak, 8 июня 2018, 16:45 yndx-antoshkka, изначально подразумевалось, что implementation будет просто сокращать написание определений. В случае, если программист пытается что-то определить через implementation секцию, чего нет в объявлении, то компилятор будет бросать теже ошибки, что и сейчас, если программист попытается сделать тоже самое с помощью текущего синтаксиса (выше я писал примеры с несколькими ошибками).

Были мысли, что в implementation блоке компилятор может разрешать писать, например, приватные и статические методы. Но это похоже на дополнительную фунциональность, которой сейчас нет в c++, если это стоит добавлять в рамках текущего предложения, то - да, надо подумать и проработать что и как можно реализовать. Это введёт pimpl идиому в язык.

Трудности не проблема

Если отказаться от дополнительного функционала, то поступило предложение сделать синтаксис следующим: namespace class object {...} в таком случае можно исключить введения нового ключевого слова.

Mikhail Shostak, 8 июня 2018, 16:58 Точнее что-то похожее на pimpl

yndx-antoshkka, 13 июня 2018, 12:04 Mikhail Shostak, начать стоит откуда-то отсюда http://eel.is/c++draft/basic.def . Посмотрите, насколько просто будет добавить вашу идею.

Подготовьте примеры покороче, попробуйте оформить вашу идею на английском и закиньте на обсуждение ещё вот сюда: https://groups.google.com/a/isocpp.org/forum/#!forum/std-proposals

Там сидят разработчики компиляторов, они смогут дать поелзную критику о возможности имплементировать данное предложение и о непротиворечивости предложенной грамматики.

Если всё будет ок - то чтоит начать писать предложение, ка кописано вот тут: https://stdcpp.ru/podgotovka-predlozheniya-v-standart-c-instruktsiya

Готов консультировать по любым вопросам :)

WPMGPRoSToTeMa, 20 июня 2018, 18:02 Будет ли в этом смысл если добавят модули?

Виктор Губин, 20 июня 2018, 20:01 Как-то больно ObjectPacal получается (и его NexT/Apple и Borand наследники).

Тогда и with (заменив на using) добавить до кучи :) Что-бы можно было делать что-то вроде

struct Vec3 {

  int x,y,z;

};

Vec3 v = {-1,0,1};

using(v) {

  x *= 2;

  y *= 2;

  z *= 2;

}

// foo.hpp

namespace bar {

class Foo {
public:
   Foo(const Foo&) = default;
   Foo& operator=(const Foo&) = default;
   Foo& operator+(const Foo& rhs); 
    Foo(); 
   ~Foo();
    void foo();
};

}

// foo.cpp

namespace bar {

using(Foo) { 

  Foo() 
  {
   // TODO: implement
  }

  ~Foo() 
  {
   // TODO: implement
  }  

  void foo() 
  {
    // TODO: implement
  }

  // compile time error Foo::foo already defined
  static void foo () {
  }

  Foo& operator+(const Foo& rhs) {
    // TODO: implement
    return *this;
  } 

}