7

How to check whether a certain type is a complete type in a certain .cpp?

template<class T>class Test{
    //some fields
    void(*functor)(T*) =[](T*){}; 
    //^ will be written by some .cpp that can access T as complete-type 
    T* t=nullptr;
    void fComplete(){    
        delete t;     //faster
        /** ^ some code that use complete type*/    
    }
    void fForward(){
        functor(t);   //slower
        /** ^ some code that forward declaration is enough*/   
    }
    void f(){  
        /*if(T is complete type){    
            fComplete();
        }else fForward();*/
    }
};

demo

It will be useful when I want to prematurely optimize a delete function in my custom smart pointer.

Can anyone confirm that it is impossible?
I am not asking for a workaround (but I don't mind) - this question is just my curiosity.

Guillaume Racicot
  • 39,621
  • 9
  • 77
  • 141
javaLover
  • 6,347
  • 2
  • 22
  • 67
  • I had hope that this would work but sadly it doesn't... https://ideone.com/nEGsZu – Curious Jun 03 '17 at 04:08
  • You can make work things depending on whether a type is complete or not, but you template must ALWAYS evaluate to the same thing, or there will ODR violation. – Guillaume Racicot Jun 03 '17 at 04:16
  • If you evaluate with you type trait that a type is incomplete, then in all you translation units your trait must result in the same result. – Guillaume Racicot Jun 03 '17 at 04:18
  • 3
    @Curious sorry if it wasn't clear. It's a comment to the question. Attempting to branch at compile-time depending on if a type is complete or not is a bad idea, especially if that type might be complete in another translation unit. – Guillaume Racicot Jun 03 '17 at 04:45
  • @Guillaume Racicot My gut also tells me (lightly) that it is a bad idea, but I can't find any specific reason. If you don't mind, may you share some reasons please? – javaLover Jun 04 '17 at 10:48

2 Answers2

7

This works

#include <iostream>
#include <type_traits>

using namespace std;

class Incomplete;
class Complete {};

template <typename IncompleteType, typename = std::enable_if_t<true>>
struct DetermineComplete {
    static constexpr const bool value = false;
};

template <typename IncompleteType>
struct DetermineComplete<
        IncompleteType,
        std::enable_if_t<sizeof(IncompleteType) == sizeof(IncompleteType)>> {
    static constexpr const bool value = true;
};

int main() {
    cout << DetermineComplete<Complete>::value << endl;
    cout << DetermineComplete<Incomplete>::value << endl;
    return 0;
}

Note I like to use std::enable_if_t for the same effect as void_t until that is available instead of writing its implementation myself everywhere.

Note Do take a look at the other answer as well about ODR. They bring up a valid point that you should consider before using this.

Curious
  • 20,870
  • 8
  • 61
  • 146
  • Now evaluate that in two contexts, one where it is complete and one where not, and I suspect your program becomes ill formed, no diagnoatic required, due to ODR violations. I believe this holds even cross compilation unit. So "works" seems a bit if a stretch. – Yakk - Adam Nevraumont Jun 05 '17 at 08:09
  • @Yakk note added – Curious Jun 05 '17 at 12:03
2

There's a rule in C++ called ODR. The very basics of this rule (from my understanding) is that something can have as many declarations as you want, but only one definition. It seems simple, but with templates and inline function, is quite easy to break it.

With templates, multiple definition is inevitable. Instantiation of the same template will happen in all translation unit that uses it. It seems against the one definition rule, but for inline and templated entities, the rule is extended. Here's a paragraph on cppreference:

There can be more than one definition in a program, as long as each definition appears in a different translation unit, of each of the following: class type, enumeration type, inline function with external linkage inline variable with external linkage (since C++17), class template, non-static function template, static data member of a class template, member function of a class template, partial template specialization, as long as all of the following is true:

  • each definition consists of the same sequence of tokens (typically, appears in the same header file)

  • name lookup from within each definition finds the same entities (after overload-resolution), except that constants with internal or no linkage may refer to different objects as long as they are not ODR-used and have the same values in every definition.

  • overloaded operators, including conversion, allocation, and deallocation functions refer to the same function from each
    definition (unless referring to one defined within the definition)

  • the language linkage is the same (e.g. the include file isn't inside an extern "C" block)

  • the three rules above apply to every default argument used in each definition

  • if the definition is for a class with an implicitly-declared constructor, every translation unit where it is odr-used must call the same constructor for the base and members

  • if the definition is for a template, then all these requirements apply to both names at the point of definition and dependent names at the point of instantiation

If all these requirements are satisfied, the program behaves as if there is only one definition in the entire program. Otherwise, the behavior is undefined.

In short, if any function template expands to slightly different things in some translation units, you end up in the UB land. Trust me, debugging ODR violation is the worst, because your program might work for a long time, and suddenly crash when changing some compilation options, like optimisations.

In your particular case, you want to detect if a type is complete or not to change the definition of a function. Since in some places you might have a complete type and instantiate that function, you'll end up with multiple and different definition of that function.

Be careful with macros too. If some macro definition changes in only some translation and you use that macro in a template or inline function, you violate ODR, since the function won't consist of the exact same tokens.


Now, I acknowledge that other answers also are useful indeed. Detecting whether a type is complete is not entirely useless. I use it in my code. I use it to provide nice diagnostics with static_assert, which even some implementations of the STL do (unique_ptr destructor in GCC's STL).

Toby Speight
  • 27,591
  • 48
  • 66
  • 103
Guillaume Racicot
  • 39,621
  • 9
  • 77
  • 141
  • I do not think you need a function template: the detection idiom of `is_incomplete` must be the *same8 in every translation unit. This means the onky way for this to be legal is if it is incomplete everywhere or nowhere, which means the OP's design is flawed fundamentally. – Yakk - Adam Nevraumont Jun 05 '17 at 12:23