0

I want to present a "pattern of mixin based structure"(is this even a term?) but not quite sure if it would hold up in "some situation". Basic idea is to generate "type using template class" that multiply inherit mixins. So the type declaration would look like: typedef BaseType<Mixin1, Mixin2, MixinN> Type1; Some accomplishments by the approach:

  • Type1's special feature like operator overloads and Constructor overloads are always available.
  • Explicit type casting overhead is abstracted away by BaseType.
  • C++ multiple implicit conversion barrier is not a problem.

Usual template mixin approach form here looks like: template<class Base> class Printing : public Base {...}. Main drawback for me with this approach:

  • It is necessary to explicitly cast Printing to Base to use some of Base's special features, Or have to provide those overloads explicitly (I know it would just be a matter of one line of codes). But in some situation it would be irritating.

That is why I have come up with the idea to generate the base. Please take a look at the implementation ("some situation"):

#include <iostream>
#include <functional>

#ifdef QT_CORE_LIB
#include <QString>
#endif


template<template<class> class... mixin_t>
class StringType : public mixin_t<StringType<mixin_t...>>...
{
    std::string _value;

public:
    StringType() : _value("") {}

    StringType(const StringType &other) = default; // Copy

    StringType(StringType &&other) = default; // Move

#ifdef QT_CORE_LIB
    StringType(const QString &value) { this->_value = value.toStdString(); }
#endif

    StringType(const std::string &value) { _value = value; }

    StringType(const char *value) { _value = value; }

    template<template<class> class T>
    StringType(const StringType<T> &value)
    {
        _value = static_cast<const std::string &>(value);
    }


    StringType &operator=(const StringType &rhs) = default; // copy assign
    StringType &operator=(StringType &&rhs) = default; // Move assign


#ifdef QT_CORE_LIB
    operator QString() const { return QString::fromStdString(_value);}
#endif

    operator std::string() const { return _value; }

    operator const char *() const{ return _value.c_str(); }
};




template<class this_t> struct _empty_mixn {};

template<class this_t> struct ToStringMixin
{
    this_t toString() const { return *static_cast<const this_t *>(this); }
};

template<class this_t> struct StringPrinterMixin
{
    void print() const
    {
        std::cout << "From the printer: " << *static_cast<const this_t *>(this);
    }
};




typedef StringType<_empty_mixn> String;
typedef StringType<ToStringMixin> Message;
typedef StringType<ToStringMixin, StringPrinterMixin> PrinterAttachedString;




int main()
{
    Message msg1(String("msg1\n"));
    std::cout << msg1;
    std::cout << "toString() : " << msg1.toString();

    Message msg2 = String("msg2\n");
    std::cout << msg2;
    std::cout << "toString() : " << msg2.toString();

    Message msg3(std::string("msg3\n"));
    std::cout << msg3;
    std::cout << "toString() : " << msg3.toString();

    Message msg4 = std::string("msg4\n");
    std::cout << msg4;
    std::cout << "toString() : " << msg4.toString();

    Message msg5("msg5\n");
    std::cout << msg5;
    std::cout << "toString() : " << msg5.toString();

    Message msg6 = "msg6\n";
    std::cout << msg6;
    std::cout << "toString() : " << msg6.toString();

    std::cout << "\n---------------------\n\n";

    PrinterAttachedString str1(String("str1\n"));
    std::cout << str1;
    std::cout << "toString() : " << str1.toString();
    str1.print();

    PrinterAttachedString str2 = String("str2\n");
    std::cout << str2;
    std::cout << "toString() : " << str2.toString();
    str2.print();

    PrinterAttachedString str3(std::string("str3\n"));
    std::cout << str3;
    std::cout << "toString() : " << str3.toString();
    str3.print();

    PrinterAttachedString str4 = std::string("str4\n");
    std::cout << str4;
    std::cout << "toString() : " << str4.toString();
    str4.print();

    PrinterAttachedString str5("str5\n");
    std::cout << str5;
    std::cout << "toString() : " << str5.toString();
    str5.print();

    PrinterAttachedString str6 = "str6\n";
    std::cout << str6;
    std::cout << "toString() : " << str6.toString();
    str6.print();

    return 0;
}

So, my questions:

  • Would it be practical use this in a situation where operator overloading/implicit casting feature necessary?
  • Does it seem, there would be a necessity of virtual inheritance?
  • Are there any other implementation like this (My search was a failure)?
  • Finally, is there a thing called "meta mixin" that would provide a type's special features?

Edit: In response to Phil1970's answer:

I am going to start with the answer to the question 3.

  • This approach leads to class proliferation: I totally agree. One big drawback I have to admit.
  • Increases coupling. Not sure how it increases coupling. *1
  • The rests marked there, I believe is not applicable due to the fact that StringType is quite final. And StringType does not know or about mixed class for real. *1

Now for the answer to the question no 1.

  • It is usually best to avoid implicit conversion.
  • The rests to me is ok as long as it is final. *2

With previous question gone (huge thanks to Phil) arose new questions.

  • *1: It is just one header-only, StringStyle does not depend on mixins and I see no reason to be so. And certainly this it can use private header if somehow becomes necessary. Then how it enforcing coupling?
  • *2: Just looking for opinions or to get me corrected.

Thanks a lot.

  • I would say it is the wrong approach… Use a namespace with free function or if the code is more complex, use an helper class. For example: `namespace Printer { void print(const string &s) { std::cout << "From the printer: " << s; } }` – Phil1970 Nov 16 '18 at 01:18
  • @Phil1970 `print()` or `toString()` is rally not my concern. Please see in my code that operator overloading (assignment & casting) and constructor conversion is the main feature here since it wraps different string implementation. Will be very glad if reconsider to check again my approach. Thanks. – Shabbir Ahmed Nov 16 '18 at 01:41
  • 1
    Well your implementation forces UB. So that is bad. – Yakk - Adam Nevraumont Nov 16 '18 at 01:58
  • @Yakk-AdamNevraumont Sorry sir, I am not expert enough to figure out which part(s) forcing UB. Can you please give me a slight hint so I can improvise if possible. – Shabbir Ahmed Nov 16 '18 at 02:09
  • @Yakk-AdamNevraumont Wow, how did that (`mixin_t>...`) error let me compile? – Shabbir Ahmed Nov 16 '18 at 02:12
  • 1
    @shabb CRTP is unsafe and unchecked. – Yakk - Adam Nevraumont Nov 16 '18 at 03:04
  • @ShabbirAhmed I wrote a complete answer that try to answer each of your question and explain why that approach is a poor design that will make the code hard to maintain. Abuse of multiple inheritance and CRTP create more problem than they solve. Prefer simple code that avoid those issues even if sometime you have to write a little more code. With editor that auto-complete, have to type a few more word should not be a major concern. – Phil1970 Nov 16 '18 at 03:22
  • @Yakk-AdamNevraumont "CRTP is unsafe and unchecked", but can't see how mixin be defined without CRTP sir. – Shabbir Ahmed Nov 16 '18 at 07:09
  • @shabbir you are passing a derived type to a base class then casting to the derived type within the base class? Your mixin templates are just variardic crtp base templates. – Yakk - Adam Nevraumont Nov 16 '18 at 09:18
  • Inheritance is a very strong coupling. See for example: https://stackoverflow.com/questions/49002/prefer-composition-over-inheritance. – Phil1970 Nov 17 '18 at 04:48
  • @Phil1970 Please, take a look, this pattern does not change the concept of mixin. Also this is the same mixin described here: http://www.drdobbs.com/cpp/mixin-based-programming-in-c/184404445 after `static_cast(this)`. Base `StringType` may be inheriting mixin **but** it never **actively** responsible for coupling. If it would then it would require the "base type info" ~ header(?). Of course `StringType` can be misused by mixins. But that is up to developer, not user. – Shabbir Ahmed Nov 17 '18 at 09:52
  • For the coupling, it is more for the final types like ˋMessage` and ˋPrinterAttachedString` that it become a maintenance problem if you have a lot of mixins and string types. – Phil1970 Nov 17 '18 at 17:33

1 Answers1

-1

For your question:

  • It is usually best to avoid implicit conversion. Also you won't be able to reuse std::string operators like +, += with that kind of approach without adding a lot one line function. The wrapper class bring you nothing except adding more conversions as you would then use you new string type and with the mixin approach, this is even worst as you need to also convert between your own types.
  • Why would you use virtual inheritance? Do you really want to derive from multiple classes that have a common base and that have their own data.
  • As this is a bad design, you probably won't find many people doing it. Your design increase coupling, lead to class proliferation, increase type conversions and make maintenance harder among other things.
  • I believe, there is no such thing.

For simple functions like those above, the preferred approach would be to define a namespace (or many if you have a lot of functions that could somehow be categorized like maybe file name manipulation) and then have free functions inside it.

By using a namespace, you have a few advantages:

  • If you call a lot of functions, you can always add an using statement inside your function or source file (never in a header file).
  • Auto suggestion will work well to find those function.

If some of the original mixin maintain state, then you should do an helper class. This could be the case for a class like an HTML builder that might have functions like AddTag, Add Attribute, AddEncodedUrl etc that could be used to create an HTML document.

One big advantage of this approach is that coupling is much looser than in your design. For example, a file pair (header and source) would contains all functions used for the Printer. If you need that, you don't have to create a new class that use some combination of mixin.

One big problem with your approach, is that with time you will have a lot of different StringType<…> If you have 5 mixins that could be used, you have 2^5 = 32 classes. At that point, it is almost sure that you will often need the mixin you didn't include and then you have cascading change if the call it deep. And if you use template everywhere then you will have compilation slowdown and probably some code bloat.

Implicit conversion is also considered to be best avoid in most cases by most experts. If you have multiple conversion from and to many classes, at some point you will have unexpected conversion or ambiguities. Making some conversion explicit can limit the problem. Usually is it best to use explicite conversion as it was done by experts in std::string. You have to call member function c_str() if you want a C style string.

For example, since your StringType class define conversion to both const char * and QString, then if you have a method that accept both (maybe an Append function), then you have a conflict.

If you really want conversion, then use named method instead (for ex. AsQString(), c_str(), tostdstring()...). It help ensure that all conversion are intended. It make it easier to find them and it is certainly better that explicit cast like you have done in a few place in your code. While static_cast and other casts are sometime useful, then can also hide some problem when code is refactored as in some case, the cast might compile while not being correct. This would be the case if you cast to a derived class and at some point decide to change the derived class for something else and forget to update some casts.

You should select the most appropriate string for your application and do conversion when required. In a large application, you might use one type for the UI (ex. CString or QString) while using standard string in librairies that are shared across platforms or with third party library. Some time those libraries have their own string class too. Your selection should try minimize useless conversions.

Phil1970
  • 2,605
  • 2
  • 14
  • 15
  • I can't thank you enough for your precious time. If you feel like to spend more time please check my updated questions. Have a nice day. – Shabbir Ahmed Nov 16 '18 at 07:06