Thread: Persistent compile-time state

  1. #1
    C++まいる!Cをこわせ!
    Join Date
    Oct 2007
    Location
    Inside my computer
    Posts
    24,654

    Persistent compile-time state

    So I've been walking along the edges of cutting edge metatemplate programming lately, but I've hit a snag, and figured maybe someone here might have an idea...

    So, anyway, first the problem.
    The problem is that there is some type T whose properties I want to check. In the class constructor, this type T is known and I can check it just fine. But in the copy constructor, the type T is unknown (incomplete) due to it being a forward declaration. So I can't check its properties in the copy constructor and I want to check if type T is copyable or not.

    This problem is easily reproduced by:
    Code:
    class foo
    {
        foo(); // Forward declare this and define in source file to ensure type T is known in the constructor
        struct Impl;
        MyContainer<Impl> hello;
    }
    foo.cpp:
    Code:
    struct foo::Impl
    {
        // ...
    }
    
    foo::foo() { /* ... */ }
    Container.cpp:
    Code:
    template<typename T>
    MyContainer::MyContainer()
    {
        // Type T is known, so do some checks
    }
    
    MyContainer::MyContainer(const MyContainer& that)
    {
        // Because copy constructor is not forward declaration, it is instantiated in the header file, so type T is unknown.
        // But I want to check its properties, especially to check if T is copyable or not. So how do I solve it?
    }
    My only possible idea on how to do this so far is to somehow remember state from the constructor (at compile time), such that the copy constructor can check this. But I haven't found a way how. Maybe it's not possible, but if it, I'd be very interested in knowing the how.

    The only other way I can think of is let the user specify whether it's copyable or not as a parameter and verify that in the constructor so the user doesn't lie. But I'd rather not do that.

    If there's some alternate way I'm not thinking of, I am of course, all ears.

    Thanks.
    Quote Originally Posted by Adak View Post
    io.h certainly IS included in some modern compilers. It is no longer part of the standard for C, but it is nevertheless, included in the very latest Pelles C versions.
    Quote Originally Posted by Salem View Post
    You mean it's included as a crutch to help ancient programmers limp along without them having to relearn too much.

    Outside of your DOS world, your header file is meaningless.

  2. #2
    Lurking whiteflags's Avatar
    Join Date
    Apr 2006
    Location
    United States
    Posts
    9,613
    If you have to check T in the copy constructor to see if it's copyable or not, you need a rewrite. The copy constructor must do something if it has already been invoked.

    Are you going to throw an exception if T is not copyable? Oh wait, you can't... you're doing template meta-programming.

  3. #3
    C++まいる!Cをこわせ!
    Join Date
    Oct 2007
    Location
    Inside my computer
    Posts
    24,654
    Quote Originally Posted by whiteflags View Post
    The copy constructor must do something if it has already been invoked.
    Or I could fire a static_assert if people try to call the copy constructor on a non-copyable type. Hence users will get a compile-time error if they try to copy the type that is movable-only, and compiles fine otherwise.

    If a container is hosting a non-copyable item, trying to copy that container will result in a compile error, usually inside the copy constructor. Since T is unknown, we cannot decide to just leave out the copy constructor. The container must be copyable if T is copyable. All I'm doing is moving the error from possible a long layer of function calls to the copy constructor directly, saving the users long pages of errors.
    Quote Originally Posted by Adak View Post
    io.h certainly IS included in some modern compilers. It is no longer part of the standard for C, but it is nevertheless, included in the very latest Pelles C versions.
    Quote Originally Posted by Salem View Post
    You mean it's included as a crutch to help ancient programmers limp along without them having to relearn too much.

    Outside of your DOS world, your header file is meaningless.

  4. #4
    Lurking whiteflags's Avatar
    Join Date
    Apr 2006
    Location
    United States
    Posts
    9,613
    Yes but if you think about it, I've written my type, T, as something that will not be copied, and you've written something that needs to make copies.

    Your solution is to molest my type, call the copy constructor anyway, and then tell me about it.

  5. #5
    C++まいる!Cをこわせ!
    Join Date
    Oct 2007
    Location
    Inside my computer
    Posts
    24,654
    Quote Originally Posted by whiteflags View Post
    Yes but if you think about it, I've written my type, T, as something that will not be copied, and you've written something that needs to make copies.
    No. I've written something that can make copies if your T allows it. Think about it as, if you have a copyable T and my container is only moveable, you'd be upset, because you can't copy the container. The container's job is to provide the services that the type T it holds provides. If that means your type if copyable, then so should the container. If your type is only moveable, then the container shouldn't be copyable.

    Quote Originally Posted by whiteflags View Post
    Your solution is to molest my type, call the copy constructor anyway, and then tell me about it.
    My solution is not to call the copy constructor, but detect within the (container) copy constructor that you're doing something you shouldn't be doing and therefore stop it. There will be no call to T::T(const T&). There will only be a static_assert. The other way is to detect if your T is not copyable and then explicitly mark the container copy constructor as deleted. It's the same thing, except you don't get a nice error message. Yet another way is for the use to tell if the type should be copyable or not and then use that information to disable the copy constructor. But I'd still have to detect mistakes, such as saying it's copyable while it really is not. And I'd rather not the user to have specify that if possible. It should just work.
    Quote Originally Posted by Adak View Post
    io.h certainly IS included in some modern compilers. It is no longer part of the standard for C, but it is nevertheless, included in the very latest Pelles C versions.
    Quote Originally Posted by Salem View Post
    You mean it's included as a crutch to help ancient programmers limp along without them having to relearn too much.

    Outside of your DOS world, your header file is meaningless.

  6. #6
    Lurking whiteflags's Avatar
    Join Date
    Apr 2006
    Location
    United States
    Posts
    9,613
    I'm not making my point clear....

    Code:
    class NoCopies {
      private:
      NoCopies(const NoCopies&);
      NoCopies& operator=(const NoCopies&);
    };
    I think I did that right...we agree that NoCopies is a noncopyable object? Then if I write something that tries to make a copy of NoCopies, it will result in an error message because I need to access the private (and unimplemented) copy constructor or assignment operator.

    Why does your code need to do anything? I already know my mistake.

    If you've written something that needs to make copies, then accept that. But if you truly wrote something that can use noncopyable objects, then there are ways for me to do everything I need anyway.

  7. #7
    C++まいる!Cをこわせ!
    Join Date
    Oct 2007
    Location
    Inside my computer
    Posts
    24,654
    For your example, this is just about the error message. If the copy constructor in the container goes and calls lots of functions, you can get a nasty long callback in the templated error message.
    Such as:

    Error: NoCopies::NoCopies(const NoCopies&) is private.
    Instantiated from: Container::Func6();
    Instantiated from: Container::Func5();
    Instantiated from: Container::Func4();
    Instantiated from: Container::Func3();
    Instantiated from: Container::Func2();
    Instantiated from: Container::Func1();
    Instantiated from: Container::Container(const Container&)
    Instantiated from: My code

    And then you get a lot of other information such as information on template parameters, etc. And I need to drill down that call trace to find the invocation in my code that did the erroneous copy. I don't want that. I want:

    Error: NoCopies::NoCopies(const NoCopies&) is private.
    Instantiated from: Container::Container(const Container&)
    Instantiated from: My code

    The end.

    But that's the best case. Because T is instantiated from the user code (not the implementation file), T is unknown. Therefore, I can't call its copy constructor. To that end, I use the copy idiom or how you want to call it. We require that T inherits from some ICopyable interface and make a pure virtual function Clone:

    Code:
    struct ICopyable
    {
        virtual ICopyable* Clone() = 0;
    };
    Boom! Now code compiles where I try to copy a moveable only type! This is nonsensical, and I can catch it at runtime, but it should not compile in the first place. Another reason why I want to do this to catch it at compile time.
    Quote Originally Posted by Adak View Post
    io.h certainly IS included in some modern compilers. It is no longer part of the standard for C, but it is nevertheless, included in the very latest Pelles C versions.
    Quote Originally Posted by Salem View Post
    You mean it's included as a crutch to help ancient programmers limp along without them having to relearn too much.

    Outside of your DOS world, your header file is meaningless.

  8. #8
    Lurking whiteflags's Avatar
    Join Date
    Apr 2006
    Location
    United States
    Posts
    9,613
    This is nonsensical, and I can catch it at runtime, but it should not compile in the first place.
    Why not? Noncopyable types can absolutely be used properly.

  9. #9
    C++まいる!Cをこわせ!
    Join Date
    Oct 2007
    Location
    Inside my computer
    Posts
    24,654
    Well yeah, but my point was that trying to copy a moveable only type should result in a compile-time error, not a runtime error, as it would in the example I made.
    Quote Originally Posted by Adak View Post
    io.h certainly IS included in some modern compilers. It is no longer part of the standard for C, but it is nevertheless, included in the very latest Pelles C versions.
    Quote Originally Posted by Salem View Post
    You mean it's included as a crutch to help ancient programmers limp along without them having to relearn too much.

    Outside of your DOS world, your header file is meaningless.

  10. #10
    Registered User
    Join Date
    Jun 2005
    Posts
    6,815
    I'd use type traits. If you're using C++11, look up std::is_copy_constructible() and std::is_copy_assignable() in <type_traits>.

    These are compile time tests.
    Right 98% of the time, and don't care about the other 3%.

    If I seem grumpy or unhelpful in reply to you, or tell you you need to demonstrate more effort before you can expect help, it is likely you deserve it. Suck it up, Buttercup, and read this, this, and this before posting again.

  11. #11
    C++まいる!Cをこわせ!
    Join Date
    Oct 2007
    Location
    Inside my computer
    Posts
    24,654
    I already use them elsewhere, but they require T to be known, which it is not in the copy constructor. Thereby the problem.
    Quote Originally Posted by Adak View Post
    io.h certainly IS included in some modern compilers. It is no longer part of the standard for C, but it is nevertheless, included in the very latest Pelles C versions.
    Quote Originally Posted by Salem View Post
    You mean it's included as a crutch to help ancient programmers limp along without them having to relearn too much.

    Outside of your DOS world, your header file is meaningless.

  12. #12
    Registered User
    Join Date
    Jun 2005
    Posts
    6,815
    T is known for every instantiation of the template. That includes for all member functions of a template class, including copy constructors.
    Right 98% of the time, and don't care about the other 3%.

    If I seem grumpy or unhelpful in reply to you, or tell you you need to demonstrate more effort before you can expect help, it is likely you deserve it. Suck it up, Buttercup, and read this, this, and this before posting again.

  13. #13
    C++まいる!Cをこわせ!
    Join Date
    Oct 2007
    Location
    Inside my computer
    Posts
    24,654
    Not if you forward declare it:

    struct Impl;
    MyContainer<Impl> hello;

    The compiler will parse the container body, but not invoke any of its functions.
    The compiler will parse constructors, the assignment operators and the destructor in the file where the class is created (e.g. inside a class definition inside a header file), unless the constructors, operators and destructor are forward declared and implementation in the source file for the class definition.
    If we forward declare the constructor:

    foo(); // Forward declare this and define in source file to ensure type T is known in the constructor

    T will be known by the constructor since it is parsed in the implementation file.
    Last edited by Elysia; 01-20-2015 at 12:13 AM.
    Quote Originally Posted by Adak View Post
    io.h certainly IS included in some modern compilers. It is no longer part of the standard for C, but it is nevertheless, included in the very latest Pelles C versions.
    Quote Originally Posted by Salem View Post
    You mean it's included as a crutch to help ancient programmers limp along without them having to relearn too much.

    Outside of your DOS world, your header file is meaningless.

  14. #14
    Master Apprentice phantomotap's Avatar
    Join Date
    Jan 2008
    Posts
    5,108
    So I've been walking along the edges of cutting edge metatemplate programming lately, but I've hit a snag, and figured maybe someone here might have an idea...
    O_o

    The things one reads on the internet...

    In the class constructor, this type T is known and I can check it just fine. But in the copy constructor, the type T is unknown (incomplete) due to it being a forward declaration.
    You are wrong, or you are doing something wrong.

    So I can't check its properties in the copy constructor and I want to check if type T is copyable or not.
    Your attempt to firewall, preventing misuse, of a "pointer implementation" container is admirable. You aren't going to accomplish such a goal with simple code.

    For you to copy an object, you must know about the type. For you to call a method, you must know about the type. If you do not know about the type, the attempt to inspect the constructor or method will fail with whatever errors the compiler desires to print in any event. If you do know about the type, you can inspect the type for completeness before attempting to inspect the constructor or method allowing you to fail however you desire.

    Your approach is thus necessarily flawed. The knowledge of completeness will fail the concept because you must allow a "pointer implementation" container to instantiate with an incomplete type. Even if you could "save the state", the state would lie because the different template instantiations may have a different view of completeness. Your code would need to violate the "one definition" rule or would result in some instantiations having the wrong behavior.

    The solution for an incomplete decision is relatively simple for this particular scenario. Do not assume inheritance for the clone operation. Do not even assume a method exists. Use parametric polymorphisms for the clone operation. A function "stickies" behavior without needing to know the type.

    Code:
    namespace Nested
    {
        InternalController * Clone(...);
    }
    Code:
    template
    <
        // ...
    >
    class MyContainer
    {
        // ...
        MyContainer(typename TraitCloneable<???>::result f) // copy constructor
        {
            using namespace Nested;
            Impl * s(Clone(f.m));
            // ...
        }
        // ...
    };
    Code:
    template
    <
        // ...
    >
    class TraitCloneable
    {
        // SFINAE over `Clone(Type())' returning `InternalController'
    };
    You can build on such a foundation with a solution for inheritance.

    Code:
    ICopyable * Clone(const ICopyable &){/* ... */}
    The strategy is only useful because a "implementation pointer" container must be allowed to be instantiated with an incomplete type. A general solution, allowing instantiatation of a template using an incomplete type is rarely useful, is just "SFINAE" over the visible incompleteness of a type so an error could be raised with `static_assert' or similar.

    Soma
    “Salem Was Wrong!” -- Pedant Necromancer
    “Four isn't random!” -- Gibbering Mouther

  15. #15
    C++まいる!Cをこわせ!
    Join Date
    Oct 2007
    Location
    Inside my computer
    Posts
    24,654
    Quote Originally Posted by phantomotap View Post
    You are wrong, or you are doing something wrong.
    All I have in my defense here is that I know T is complete in the constructor and incomplete in the copy constructor. I've verified this on the MSVC compiler (not yet on gcc).
    Which means I cannot clone T via normal means, so I'm not sure how to clone the object using a normal clone function. The compiler would just parse the function, see that T is unknown and barf.

    Anyway, I'll return with a minimal example tomorrow.
    Quote Originally Posted by Adak View Post
    io.h certainly IS included in some modern compilers. It is no longer part of the standard for C, but it is nevertheless, included in the very latest Pelles C versions.
    Quote Originally Posted by Salem View Post
    You mean it's included as a crutch to help ancient programmers limp along without them having to relearn too much.

    Outside of your DOS world, your header file is meaningless.

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Replies: 2
    Last Post: 01-12-2013, 10:11 AM
  2. Persistent Segmentation Fault
    By Imanuel in forum C Programming
    Replies: 4
    Last Post: 07-26-2010, 08:29 PM
  3. Persistent connections
    By Niara in forum Networking/Device Communication
    Replies: 6
    Last Post: 09-25-2007, 05:27 AM