Bug 62255

Summary: [4.8/4.9 Regression] Introducing an unrelated template parameter causes compilation to fail
Product: gcc Reporter: Max Gerlach <m>
Component: c++Assignee: Jason Merrill <jason>
Status: RESOLVED FIXED    
Severity: normal CC: daniel.kruegler, jakub, jason
Priority: P2 Keywords: rejects-valid
Version: 4.9.1   
Target Milestone: 4.9.3   
Host: Target:
Build: Known to work: 4.6.3, 4.7.3
Known to fail: 4.8.2, 4.8.3, 4.9.1 Last reconfirmed: 2014-09-06 00:00:00
Attachments: Archive containing output.txt, arma_template_test.ii and arma_no_template_test.ii
Compiler error messages from g++ 4.9.1

Description Max Gerlach 2014-08-25 13:52:46 UTC
Created attachment 33393 [details]
Archive containing output.txt, arma_template_test.ii and arma_no_template_test.ii

Adding a template parameter to a class causes compilation to fail, even though no further use of this parameter is made.


This problem shows up by using any recent release of the Armadillo C++ library (header only, http://arma.sourceforge.net/) with gcc 4.8.2 (my system) and 4.8.3 (another system).  On g++ 4.6.3, the Intel compiler 13.1 or clang 3.4 the problem does not appear.

This is the version information from my system:

$ g++ --version
g++ (Ubuntu 4.8.2-19ubuntu1) 4.8.2
Copyright (C) 2013 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.



1) Failing compilation 

command line: g++ -c arma_template_test.cpp -Wall -Wextra -std=c++11 -save-temps &> output.txt

Please see attached error messages in output.txt and attached prepocessed file arma_template_test.ii.  The error messages in output.txt are also repeated directly in this bug report below.

Code arma_template_test.cpp:

#include <armadillo>
#include <iostream>

template<int param>
class Test {
public:
    void compute() {
        arma::mat matrix = arma::randu(10,10);
        matrix = matrix + matrix.t();
    
        arma::vec eigval;
        arma::mat eigvec;
        arma::eig_sym(eigval, eigvec, matrix);
        arma::mat exp_matrix = eigvec * arma::diagmat(arma::exp(eigval)) * arma::trans(eigvec);

        exp_matrix.print(std::cout);
    }
};

int main() {
    Test<1> test;
    test.compute();
    
    return 0;  
}



2) Successful compilation without warnings

command line: g++ -c arma_no_template_test.cpp -Wall -Wextra -std=c++11 -save-temps

Please see attached prepocessed file arma_no_template_test.ii

Code arma_no_template_test.cpp:
#include <armadillo>
#include <iostream>

class Test {
public:
    void compute() {
        arma::mat matrix = arma::randu(10,10);
        matrix = matrix + matrix.t();
    
        arma::vec eigval;
        arma::mat eigvec;
        arma::eig_sym(eigval, eigvec, matrix);
        arma::mat exp_matrix = eigvec * arma::diagmat(arma::exp(eigval)) * arma::trans(eigvec);

        exp_matrix.print(std::cout);
    }
};

int main() {
    Test test;
    test.compute();
    
    return 0;  
}




3) Error messages for failing compilation (output.txt):

In file included from /usr/include/armadillo:105:0,
                 from arma_template_test.cpp:1:
/usr/include/armadillo_bits/traits.hpp: In instantiation of ‘const bool arma::is_Mat_fixed<arma::eOp<arma::Col<double>, arma::eop_exp> >::value’:
/usr/include/armadillo_bits/diagmat_proxy.hpp:415:7:   required from ‘class arma::diagmat_proxy_check<arma::eOp<arma::Col<double>, arma::eop_exp> >’
/usr/include/armadillo_bits/glue_times_meat.hpp:851:44:   required from ‘static void arma::glue_times_diag::apply(arma::Mat<typename T1::elem_type>&, const arma::Glue<T1, T2, arma::glue_times_diag>&) [with T1 = arma::Mat<double>; T2 = arma::Op<arma::eOp<arma::Col<double>, arma::eop_exp>, arma::op_diagmat>; typename T1::elem_type = double]’
/usr/include/armadillo_bits/Mat_meat.hpp:4377:28:   required from ‘arma::Mat<eT>::Mat(const arma::Glue<T1, T2, glue_type>&) [with T1 = arma::Mat<double>; T2 = arma::Op<arma::eOp<arma::Col<double>, arma::eop_exp>, arma::op_diagmat>; glue_type = arma::glue_times_diag; eT = double]’
/usr/include/armadillo_bits/glue_times_meat.hpp:82:25:   required from ‘static void arma::glue_times_redirect2_helper<true>::apply(arma::Mat<typename T1::elem_type>&, const arma::Glue<T1, T2, arma::glue_times>&) [with T1 = arma::Glue<arma::Mat<double>, arma::Op<arma::eOp<arma::Col<double>, arma::eop_exp>, arma::op_diagmat>, arma::glue_times_diag>; T2 = arma::Op<arma::Mat<double>, arma::op_htrans>; typename T1::elem_type = double]’
/usr/include/armadillo_bits/glue_times_meat.hpp:318:81:   required from ‘static void arma::glue_times_redirect<2u>::apply(arma::Mat<typename T1::elem_type>&, const arma::Glue<T1, T2, arma::glue_times>&) [with T1 = arma::Glue<arma::Mat<double>, arma::Op<arma::eOp<arma::Col<double>, arma::eop_exp>, arma::op_diagmat>, arma::glue_times_diag>; T2 = arma::Op<arma::Mat<double>, arma::op_htrans>; typename T1::elem_type = double]’
/usr/include/armadillo_bits/glue_times_meat.hpp:412:43:   required from ‘static void arma::glue_times::apply(arma::Mat<typename T1::elem_type>&, const arma::Glue<T1, T2, arma::glue_times>&) [with T1 = arma::Glue<arma::Mat<double>, arma::Op<arma::eOp<arma::Col<double>, arma::eop_exp>, arma::op_diagmat>, arma::glue_times_diag>; T2 = arma::Op<arma::Mat<double>, arma::op_htrans>; typename T1::elem_type = double]’
/usr/include/armadillo_bits/Mat_meat.hpp:4377:28:   required from ‘arma::Mat<eT>::Mat(const arma::Glue<T1, T2, glue_type>&) [with T1 = arma::Glue<arma::Mat<double>, arma::Op<arma::eOp<arma::Col<double>, arma::eop_exp>, arma::op_diagmat>, arma::glue_times_diag>; T2 = arma::Op<arma::Mat<double>, arma::op_htrans>; glue_type = arma::glue_times; eT = double]’
arma_template_test.cpp:14:94:   required from ‘void Test<param>::compute() [with int param = 1]’
arma_template_test.cpp:22:18:   required from here
/usr/include/armadillo_bits/traits.hpp:67:23: error: non-constant in-class initialization invalid for static member ‘arma::is_Mat_fixed<arma::eOp<arma::Col<double>, arma::eop_exp> >::value’
   { static const bool value = ( is_Mat_fixed_only<T>::value || is_Row_fixed_only<T>::value || is_Col_fixed_only<T>::value ); };
                       ^
/usr/include/armadillo_bits/traits.hpp:67:23: error: (an out of class initialization is required)
/usr/include/armadillo_bits/traits.hpp:67:23: error: ‘arma::is_Mat_fixed<arma::eOp<arma::Col<double>, arma::eop_exp> >::value’ cannot be initialized by a non-constant expression when being declared
In file included from /usr/include/armadillo:301:0,
                 from arma_template_test.cpp:1:
/usr/include/armadillo_bits/diagmat_proxy.hpp: In instantiation of ‘class arma::diagmat_proxy_check<arma::eOp<arma::Col<double>, arma::eop_exp> >’:
/usr/include/armadillo_bits/glue_times_meat.hpp:851:44:   required from ‘static void arma::glue_times_diag::apply(arma::Mat<typename T1::elem_type>&, const arma::Glue<T1, T2, arma::glue_times_diag>&) [with T1 = arma::Mat<double>; T2 = arma::Op<arma::eOp<arma::Col<double>, arma::eop_exp>, arma::op_diagmat>; typename T1::elem_type = double]’
/usr/include/armadillo_bits/Mat_meat.hpp:4377:28:   required from ‘arma::Mat<eT>::Mat(const arma::Glue<T1, T2, glue_type>&) [with T1 = arma::Mat<double>; T2 = arma::Op<arma::eOp<arma::Col<double>, arma::eop_exp>, arma::op_diagmat>; glue_type = arma::glue_times_diag; eT = double]’
/usr/include/armadillo_bits/glue_times_meat.hpp:82:25:   required from ‘static void arma::glue_times_redirect2_helper<true>::apply(arma::Mat<typename T1::elem_type>&, const arma::Glue<T1, T2, arma::glue_times>&) [with T1 = arma::Glue<arma::Mat<double>, arma::Op<arma::eOp<arma::Col<double>, arma::eop_exp>, arma::op_diagmat>, arma::glue_times_diag>; T2 = arma::Op<arma::Mat<double>, arma::op_htrans>; typename T1::elem_type = double]’
/usr/include/armadillo_bits/glue_times_meat.hpp:318:81:   required from ‘static void arma::glue_times_redirect<2u>::apply(arma::Mat<typename T1::elem_type>&, const arma::Glue<T1, T2, arma::glue_times>&) [with T1 = arma::Glue<arma::Mat<double>, arma::Op<arma::eOp<arma::Col<double>, arma::eop_exp>, arma::op_diagmat>, arma::glue_times_diag>; T2 = arma::Op<arma::Mat<double>, arma::op_htrans>; typename T1::elem_type = double]’
/usr/include/armadillo_bits/glue_times_meat.hpp:412:43:   required from ‘static void arma::glue_times::apply(arma::Mat<typename T1::elem_type>&, const arma::Glue<T1, T2, arma::glue_times>&) [with T1 = arma::Glue<arma::Mat<double>, arma::Op<arma::eOp<arma::Col<double>, arma::eop_exp>, arma::op_diagmat>, arma::glue_times_diag>; T2 = arma::Op<arma::Mat<double>, arma::op_htrans>; typename T1::elem_type = double]’
/usr/include/armadillo_bits/Mat_meat.hpp:4377:28:   required from ‘arma::Mat<eT>::Mat(const arma::Glue<T1, T2, glue_type>&) [with T1 = arma::Glue<arma::Mat<double>, arma::Op<arma::eOp<arma::Col<double>, arma::eop_exp>, arma::op_diagmat>, arma::glue_times_diag>; T2 = arma::Op<arma::Mat<double>, arma::op_htrans>; glue_type = arma::glue_times; eT = double]’
arma_template_test.cpp:14:94:   required from ‘void Test<param>::compute() [with int param = 1]’
arma_template_test.cpp:22:18:   required from here
/usr/include/armadillo_bits/diagmat_proxy.hpp:415:7: error: the value of ‘arma::is_Col_fixed_only<arma::eOp<arma::Col<double>, arma::eop_exp> >::value’ is not usable in a constant expression
 class diagmat_proxy_check : public diagmat_proxy_check_redirect<T1, is_Mat_fixed<T1>::value >::result
       ^
In file included from /usr/include/armadillo:105:0,
                 from arma_template_test.cpp:1:
/usr/include/armadillo_bits/traits.hpp:60:21: note: ‘arma::is_Col_fixed_only<arma::eOp<arma::Col<double>, arma::eop_exp> >::value’ was not initialized with a constant expression
   static const bool value = ( sizeof(check<T>(0)) == sizeof(yes) );
                     ^
In file included from /usr/include/armadillo:301:0,
                 from arma_template_test.cpp:1:
/usr/include/armadillo_bits/diagmat_proxy.hpp:415:7: note: in template argument for type ‘bool’ 
 class diagmat_proxy_check : public diagmat_proxy_check_redirect<T1, is_Mat_fixed<T1>::value >::result
       ^
In file included from /usr/include/armadillo:564:0,
                 from arma_template_test.cpp:1:
/usr/include/armadillo_bits/glue_times_meat.hpp: In instantiation of ‘static void arma::glue_times_diag::apply(arma::Mat<typename T1::elem_type>&, const arma::Glue<T1, T2, arma::glue_times_diag>&) [with T1 = arma::Mat<double>; T2 = arma::Op<arma::eOp<arma::Col<double>, arma::eop_exp>, arma::op_diagmat>; typename T1::elem_type = double]’:
/usr/include/armadillo_bits/Mat_meat.hpp:4377:28:   required from ‘arma::Mat<eT>::Mat(const arma::Glue<T1, T2, glue_type>&) [with T1 = arma::Mat<double>; T2 = arma::Op<arma::eOp<arma::Col<double>, arma::eop_exp>, arma::op_diagmat>; glue_type = arma::glue_times_diag; eT = double]’
/usr/include/armadillo_bits/glue_times_meat.hpp:82:25:   required from ‘static void arma::glue_times_redirect2_helper<true>::apply(arma::Mat<typename T1::elem_type>&, const arma::Glue<T1, T2, arma::glue_times>&) [with T1 = arma::Glue<arma::Mat<double>, arma::Op<arma::eOp<arma::Col<double>, arma::eop_exp>, arma::op_diagmat>, arma::glue_times_diag>; T2 = arma::Op<arma::Mat<double>, arma::op_htrans>; typename T1::elem_type = double]’
/usr/include/armadillo_bits/glue_times_meat.hpp:318:81:   required from ‘static void arma::glue_times_redirect<2u>::apply(arma::Mat<typename T1::elem_type>&, const arma::Glue<T1, T2, arma::glue_times>&) [with T1 = arma::Glue<arma::Mat<double>, arma::Op<arma::eOp<arma::Col<double>, arma::eop_exp>, arma::op_diagmat>, arma::glue_times_diag>; T2 = arma::Op<arma::Mat<double>, arma::op_htrans>; typename T1::elem_type = double]’
/usr/include/armadillo_bits/glue_times_meat.hpp:412:43:   required from ‘static void arma::glue_times::apply(arma::Mat<typename T1::elem_type>&, const arma::Glue<T1, T2, arma::glue_times>&) [with T1 = arma::Glue<arma::Mat<double>, arma::Op<arma::eOp<arma::Col<double>, arma::eop_exp>, arma::op_diagmat>, arma::glue_times_diag>; T2 = arma::Op<arma::Mat<double>, arma::op_htrans>; typename T1::elem_type = double]’
/usr/include/armadillo_bits/Mat_meat.hpp:4377:28:   required from ‘arma::Mat<eT>::Mat(const arma::Glue<T1, T2, glue_type>&) [with T1 = arma::Glue<arma::Mat<double>, arma::Op<arma::eOp<arma::Col<double>, arma::eop_exp>, arma::op_diagmat>, arma::glue_times_diag>; T2 = arma::Op<arma::Mat<double>, arma::op_htrans>; glue_type = arma::glue_times; eT = double]’
arma_template_test.cpp:14:94:   required from ‘void Test<param>::compute() [with int param = 1]’
arma_template_test.cpp:22:18:   required from here
/usr/include/armadillo_bits/glue_times_meat.hpp:855:30: error: ‘const class arma::diagmat_proxy_check<arma::eOp<arma::Col<double>, arma::eop_exp> >’ has no member named ‘n_elem’
     const uword B_n_elem = B.n_elem;
                              ^
/usr/include/armadillo_bits/glue_times_meat.hpp:863:23: error: no match for ‘operator[]’ (operand types are ‘const arma::diagmat_proxy_check<arma::eOp<arma::Col<double>, arma::eop_exp> >’ and ‘arma::uword {aka unsigned int}’)
       const eT  val = B[col];
                       ^
/usr/include/armadillo_bits/glue_times_meat.hpp:891:30: error: ‘const class arma::diagmat_proxy_check<arma::eOp<arma::Col<double>, arma::eop_exp> >’ has no member named ‘n_elem’
     const uword B_n_elem = B.n_elem;
                              ^
/usr/include/armadillo_bits/glue_times_meat.hpp:899:29: error: no match for ‘operator[]’ (operand types are ‘const arma::diagmat_proxy_check<arma::eOp<arma::Col<double>, arma::eop_exp> >’ and ‘arma::uword {aka unsigned int}’)
       out.at(i,i) = A[i] * B[i];
                             ^
Comment 1 Conrad S 2014-08-25 14:06:08 UTC
Confirmed on g++ 4.8.3, Fedora 20, 64 bit.
gcc version 4.8.3 20140624 (Red Hat 4.8.3-1) (GCC)
Comment 2 Conrad S 2014-08-25 14:14:53 UTC
When the irrelevant template parameter is used (arma_template_test.ii), g++ thinks that "value" in the following code can't be determined at compile time:

template<typename T>
struct is_Col_fixed_only
  {
  typedef char yes[1];
  typedef char no[2];
  
  template<typename X> static yes& check(typename X::Col_fixed_type*);
  template<typename>   static no&  check(...);
  
  static const bool value = ( sizeof(check<T>(0)) == sizeof(yes) );
  };
Comment 3 Max Gerlach 2014-08-26 09:32:22 UTC
Created attachment 33397 [details]
Compiler error messages from g++ 4.9.1
Comment 4 Max Gerlach 2014-08-26 09:33:29 UTC
The bug is still present on g++ 4.9.1.


$ g++-4.9 --version
g++-4.9 (Ubuntu 4.9.1-3ubuntu2~14.04.1) 4.9.1
Copyright (C) 2014 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.


Output of 

$ g++-4.9 -c arma_template_test.cpp -Wall -Wextra -std=c++11 &> output-4.9.1.txt

has been attached.
Comment 5 Max Gerlach 2014-08-26 13:26:29 UTC
gcc 4.7.3 compiles the code without errors.

$ g++-4.7 --version
g++-4.7 (Ubuntu/Linaro 4.7.3-12ubuntu1) 4.7.3
Copyright (C) 2012 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
Comment 6 Jakub Jelinek 2014-09-05 15:06:25 UTC
This started to be rejected with r197613 (one needs -std=c++11 -mfxsr to compile it BTW).  Whether it is valid or not I'll defer to the C++ folks.
Comment 7 Jason Merrill 2014-09-06 00:05:34 UTC
The problem with this testcase is that in evaluating arma::is_Mat_fixed_only<arma::eOp<arma::Col<double>, arma::eop_exp> >::value we need to look up check<arma::eOp<arma::Col<double>, arma::eop_exp>>, which means looking up arma::eOp<arma::Col<double>, arma::eop_exp>::Mat_fixed_type, which means instantiating arma::eOp<arma::Col<double>, arma::eop_exp> if it isn't already complete.

Which means instantiating arma::Base<double, arma::eOp<arma::Col<double>, arma::eop_exp>>

Which means substituting into arma::Base_eval<elem_type, derived, arma::is_Mat<derived>::value>

Which depends on arma::is_Mat_fixed_only<arma::eOp<arma::Col<double>, arma::eop_exp> >::value, which is where we started.

So instantiating the decl ends up requiring its own definition, though the actual value does not depend on itself, so we could probably allow it.  The loop would be avoided if arma::eOp<arma::Col<double>, arma::eop_exp> is instantiated before we try to instantiate the value.

Here's a simpler testcase that doesn't happen to fail with GCC for some reason, but does with clang:

template <class T> struct B;

template <bool b>
struct C { };

template <class T>
struct A: C<B<A<T>>::value>
{
  typedef T Type;
};

template<class T>
struct B
{
  template<typename X> static int check(typename X::Type*);
  template<typename> static char check(...);
  static const bool value = (sizeof(check<T>(0)) == sizeof(int));
};

int main()
{
  return B<A<int>>::value;
}
Comment 8 Jason Merrill 2014-09-06 03:44:45 UTC
And here's a reduced testcase that GCC and EDG reject, but clang accepts.

template <typename T> struct Test {
  template <typename X> static void check(typename X::Undefined *);
  template <typename> static int &check(...);
  static const int value = sizeof (check<T>());
};
template <int> struct Sink { };
template <typename T> struct Base : Sink<Test<T>::value> {};
template <typename T> class Derived : Base<Derived<T> > {};

int i[Test<Derived<int> >::value];
Comment 9 Jason Merrill 2014-09-06 16:37:10 UTC
And one rejected by all of GCC, Clang and EDG (but also accepted by 4.7):

template <typename T> struct Test {
  template<typename X> static int check(typename X::Type*);
  template<typename> static char check(...);
  static const int value = sizeof(check<T>(0));
};
template <int> struct Sink { };  
template <typename T> class Derived : Sink<Test<Derived<T> >::value> {};
Sink<Test<Derived<int> >::value> s;

If we somehow cause Derived<int> to be instantiated before the last line, such as by declaring a Derived<int> variable, everything is fine.  We only run into trouble because it hasn't been.

r197613 caused this to start breaking because before that change we had briefly been instantiating classes in more situations, but that caused its own problems.

It's not clear to me that this needs to be ill-formed; the value is not dependent on itself, it's just an accident of instantiation context.  We could decide to just handle this.  I think I'll make that change and also bring it up with the committee.
Comment 10 Jason Merrill 2014-09-09 12:00:16 UTC
Author: jason
Date: Tue Sep  9 11:59:45 2014
New Revision: 215062

URL: https://gcc.gnu.org/viewcvs?rev=215062&root=gcc&view=rev
Log:
	PR c++/62255
	* pt.c (instantiate_decl): Handle recursive instantiation of
	static data member.

Added:
    trunk/gcc/testsuite/g++.dg/template/recurse4.C
Modified:
    trunk/gcc/cp/ChangeLog
    trunk/gcc/cp/pt.c
Comment 11 Jakub Jelinek 2014-12-19 13:28:13 UTC
GCC 4.8.4 has been released.
Comment 12 Jason Merrill 2015-02-26 02:44:29 UTC
Author: jason
Date: Thu Feb 26 02:43:58 2015
New Revision: 220997

URL: https://gcc.gnu.org/viewcvs?rev=220997&root=gcc&view=rev
Log:
	PR c++/62255
	* pt.c (instantiate_decl): Handle recursive instantiation of
	static data member.

Added:
    branches/gcc-4_9-branch/gcc/testsuite/g++.dg/template/recurse4.C
Modified:
    branches/gcc-4_9-branch/gcc/cp/ChangeLog
    branches/gcc-4_9-branch/gcc/cp/pt.c
Comment 13 Jason Merrill 2015-03-19 14:33:35 UTC
Fixed for 4.9.3/5.